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 format: 1
handler: handler:
commit: 767bc6d5ae87459cc6534ddcf91054d28fdd252c commit: bae4684bd5ab659bd78ebd990cc68ecba8a36669
branch: master branch: master
lock: false lock: false
repository_class: Piston::Git::Repository 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 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> <pre><code>
$('div').concrete('foo.bar', function($){ return { $.concrete('foo.bar', function($){
$('div').concrete({
baz: function(){} baz: function(){}
}}); });
});
</code></pre> </code></pre>
You can then call these functions like this: You can then call these functions like this:
<pre><code> <pre><code>
@ -182,9 +184,11 @@ This is particularly useful for events, because given this:
onclick: function(){ this.css({backgroundColor: 'blue'}); } onclick: function(){ this.css({backgroundColor: 'blue'}); }
}); });
$('div').concrete('foo', function($){ return { $.concrete('foo', function($){
$('div').concrete({
onclick: function(){ this.css({color: 'green'}); } onclick: function(){ this.css({color: 'green'}); }
}}); });
});
</code></pre> </code></pre>
Clicking on a div will change the background **and** foreground color. 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 Where they don't exist, they will be looked up in the base namespace
<pre><code> <pre><code>
$('div').concrete('foo', { $.concrete('foo', function($){
bar: function() { this.baz(); this.qux(); } $('div').concrete({
baz: function() { console.log('baz'); } bar: function() { this.baz(); this.qux(); }
baz: function() { console.log('baz'); }
})
}) })
$('div').concrete({ $('div').concrete({
@ -220,12 +226,15 @@ Note that 'exists' means that a function is declared in this namespace for _any_
And the concrete definitions And the concrete definitions
<pre><code> <pre><code>
$('div').concrete('foo', { $.concrete('foo', function($){
bar: function() { this.baz(); } $('div').concrete({
bar: function() { this.baz(); }
});
$('span').concrete({
baz: function() { console.log('a'); }
});
}) })
$('span').concrete('foo' {
baz: function() { console.log('a'); }
$('div').concrete({ $('div').concrete({
baz: function() { console.log('b'); } 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 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 h4. Nesting 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>
You can also nest declarations. In this next example, we're defining the functions $().concrete('zap').bar() and $().concrete('zap.pow').baz() 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 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> <pre><code>
$('div').concrete('foo', { $.concrete('foo', function($){
bar: function() { this.concrete('.').baz(); } $('div').concrete({
bar: function() { this.concrete('.').baz(); }
})
}) })
</code></pre> </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 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> <pre><code>
$('div').concrete('foo', { $.concrete('foo', function($){
bar: function() { console.log('a'); } $('div').concrete({
bar: function() { console.log('a'); }
})
}) })
$('div').concrete('foo', function(){ $('div').concrete('foo', function(){
this.bar(); this.bar();
this.bar();
this.bar();
}); });
</code></pre> </code></pre>

View File

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

View File

@ -2,71 +2,77 @@
describe 'Concrete' describe 'Concrete'
describe 'Basics' describe 'Basics'
before before
$.concrete.warningLevel = $.concrete.WARN_LEVEL_BESTPRACTISE; $.concrete.warningLevel = $.concrete.WARN_LEVEL_BESTPRACTISE;
$('body').append('<div id="dom_test"></div>') $('body').append('<div id="dom_test"></div>');
end end
after after
$('#dom_test').remove() $('#dom_test').remove();
end end
before_each before_each
$.concrete.clear_all_rules() $.concrete.clear_all_rules();
$('#dom_test').html('<div id="a" class="a b c"></div><div id="b" class="c d e"></div>') $('#dom_test').html('<div id="a" class="a b c"></div><div id="b" class="c d e"></div>');
end end
it 'can attach and call a base function' it 'can attach and call a base function'
$('#a').concrete({ $('#a').concrete({
foo: function(){return this.attr('id');} foo: function(){return this.attr('id');}
}); });
$('.a').foo().should.equal 'a' $('.a').foo().should.equal 'a'
end end
it 'can attach and call several base functions' it 'can attach and call several base functions'
$('#a').concrete({ $('#a').concrete({
foo: function(){return 'foo_' + this.attr('id');}, foo: function(){return 'foo_' + this.attr('id');},
bar: function(){return 'bar_' + this.attr('id');} bar: function(){return 'bar_' + this.attr('id');}
}); });
$('.a').foo().should.equal 'foo_a' $('.a').foo().should.equal 'foo_a'
$('.a').bar().should.equal 'bar_a' $('.a').bar().should.equal 'bar_a'
end end
it 'can attach and call a namespaced function' it 'can attach and call a namespaced function'
$('#a').concrete('bar', function($){return{ $.concrete('bar', function($){
foo: function(){return this.attr('id');} $('#a').concrete({
}}); foo: function(){return this.attr('id');}
$('.a').concrete('bar').foo().should.equal 'a' });
});
$('.a').concrete('bar').foo().should.equal 'a'
end end
it 'can attach and call a nested namespaced function' it 'can attach and call a nested namespaced function'
$('#a').concrete('qux.baz.bar', function($){return{ $.concrete('qux.baz.bar', function($){
foo: function(){return this.attr('id');} $('#a').concrete({
}}); foo: function(){return this.attr('id');}
$('.a').concrete('qux.baz.bar').foo().should.equal 'a' });
});
$('.a').concrete('qux.baz.bar').foo().should.equal 'a'
end end
it 'can call two functions on two elements' it 'can call two functions on two elements'
var res = [] var res = []
$('#a').concrete({ $('#a').concrete({
foo: function(){res.push(this.attr('id'));}, foo: function(){res.push(this.attr('id'));}
}) });
$('#b.c').concrete({ $('#b.c').concrete({
foo: function(){res.push(this.attr('id'));}, foo: function(){res.push(this.attr('id'));}
}) });
$('#dom_test div').foo(); $('#dom_test div').foo();
res.should.eql ['b', 'a'] res.should.eql ['b', 'a']
end end
it 'can call two namespaced functions on two elements' it 'can call two namespaced functions on two elements'
var res = [] var res = []
$('#a').concrete('bar', function($){return{ $.concrete('bar', function($){
foo: function(){res.push(this.attr('id'));}, $('#a').concrete({
}}) foo: function(){res.push(this.attr('id'));}
$('#b.c').concrete('bar', function($){return{ });
foo: function(){res.push(this.attr('id'));}, $('#b.c').concrete({
}}) foo: function(){res.push(this.attr('id'));}
});
});
$('#dom_test div').concrete('bar').foo(); $('#dom_test div').concrete('bar').foo();
res.should.eql ['b', 'a'] res.should.eql ['b', 'a']
end end
end 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' it 'internal to namespace, will look up functions in base when not present in namespace'
var res = [] var res = []
$('#a').concrete({ $('#a').concrete({
foo: function(){res.push(1);}, foo: function(){res.push(1);}
}) })
$('#a').concrete('bar', function($){return{ $('#a').concrete('bar', function($){return{
bar: function(){res.push(2); this.foo();} 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' it 'internal to namespace, will not look up functions in base if present in namespace, even when not applicable to selector'
var res = [] var res = []
$('#a').concrete('bar', function($){return{ $('#a').concrete('bar', function($){return{
foo: function(){this.bar();}, foo: function(){this.bar();}
}}) }})
$('#a').concrete({ $('#a').concrete({
bar: function(){res.push(1);}, bar: function(){res.push(1);}
}) })
$('span').concrete('bar', function($){return{ $('span').concrete('bar', function($){return{
bar: function(){res.push(2);} bar: function(){res.push(2);}
@ -227,10 +227,10 @@ describe 'Concrete'
it 'using block functions' it 'using block functions'
var res = [] var res = []
$('#a').concrete({ $('#a').concrete({
foo: function(){res.push(1);}, foo: function(){res.push(1);}
}) })
$('#a').concrete('bar', function($){return{ $('#a').concrete('bar', function($){return{
foo: function(){res.push(3);}, foo: function(){res.push(3);}
}}) }})
$('#dom_test div').foo(); $('#dom_test div').foo();

View File

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

View File

@ -1,8 +1,8 @@
--- ---
format: 1 format: 1
handler:
commit: 8fe63b186e349433b6da1cd6ec4c9a751fefb13e
branch: master
lock: false lock: false
repository_class: Piston::Git::Repository repository_class: Piston::Git::Repository
handler:
commit: 75336bc94061b818f90361ff163436c8eb61d4c3
branch: tags/2.8.4
repository_url: git://github.com/visionmedia/jspec.git 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 === 2.8.4 / 2009-08-02
* Fixed error thrown when a module has no utilities * Fixed error thrown when a module has no utilities

View File

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

View File

@ -33,6 +33,7 @@ and much more.
* Method Stubbing * Method Stubbing
* Shared behaviors * Shared behaviors
* Profiling * Profiling
* Interactive Shell
* Ruby on Rails Integration * Ruby on Rails Integration
* Tiny (15 kb compressed, 1300-ish LOC) * 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 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 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 also available as a Ruby Gem (though this is not required), which also
provides the `jspec` executable. To install execute: provides the `jspec` executable. First install [Gemcutter](http://gemcutter.org/) then execute:
$ gem sources -a http://gems.github.com (if you have not previously done so) $ sudo gem install jspec
$ sudo gem install visionmedia-jspec
At which point you may: At which point you may:
$ jspec init myproject $ 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 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). loaded using the exec method (unless you are using the grammar-less alternative).
Below is an example: 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 query string, or passing a hash to run(). For example
JSpec.options.failuresOnly = true, and ?failuresOnly=1 will both work. 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) * 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) * 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) * reportToId {string} an element id to report to when using the DOM formatter (DOM)
* verbose {bool} verbose server output, defaults to false (Server)
== Matchers == Matchers
@ -188,6 +196,44 @@ JSpec.options.failuresOnly = true, and ?failuresOnly=1 will both work.
- be_disabled - be_disabled
- be_selected - be_selected
- be_checked - 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 == Proxy Assertions
@ -242,9 +288,9 @@ on any object when using the JSpec grammar:
* Core * Core
- an_instance_of used in conjunction with the 'receive' matcher - an_instance_of used in conjunction with the 'receive' matcher
- mockRequest mock a request (requires jspec.xhr.js) - mockRequest, mock_request mock a request (requires jspec.xhr.js)
- unmockRequest unmock requests (requests jspec.xhr.js) - unmockRequest, unmock_request unmock requests (requests jspec.xhr.js)
* jQuery * jQuery
@ -530,11 +576,13 @@ example view lib/jspec.jquery.js.
The following methods or properties are utilized by JSpec: The following methods or properties are utilized by JSpec:
- init : called to initialize a module - name : module name string
- utilities : hash of utility functions merged with JSpec.defaultContext - init : called to initialize a module
- matchers : hash of matchers merged with JSpec's core matchers via JSpec.addMatchers() - formatters : hash of formatters merged with JSpec.formatters
- DSLs : hash of DSL methods; for example DSLs.snake contains before_each, after_each, etc. - utilities : hash of utility functions merged with JSpec.defaultContext
Where as DSLs.camel may contain beforeEach, afterEach, etc. - 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 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 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 - loading(file) : loading a file : returning 'stop' will prevent loading
- executing(file) : executing a file : returning 'stop' will prevent execution - executing(file) : executing a file : returning 'stop' will prevent execution
- posting(data, url) : posting data to a url : returning 'stop' will prevent request - 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 - 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) - 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) - 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, and auto-testing each browser, however results are centralized to the terminal,
removing the need to manually view each browser's output. removing the need to manually view each browser's output.
Initialize project with: When utilizing the server if a file named spec/jspec.rb (or jspec/jspec.rb for rails)
$ jspec init myproject 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 $ 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 == Ruby on Rails
No additional gems are required for JSpec to work with rails, although 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 $ 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 == Known Issues
* Tabs may cause a parse error. To prevent this use 'soft tabs' (setting in your IDE/Editor) * 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' return 'html'
}) })
== Additional JSpec Modules
* JSocka stubbing http://github.com/gisikw/jsocka/tree/master
== More Information == More Information
* IRC Channel irc://irc.freenode.net#jspec
* Featured article in JSMag: http://www.jsmag.com/main.issues.description/id=21/ * Featured article in JSMag: http://www.jsmag.com/main.issues.description/id=21/
* Syntax comparison with other frameworks http://gist.github.com/92283 * Syntax comparison with other frameworks http://gist.github.com/92283
* Get the TextMate bundle at https://github.com/visionmedia/jspec.tmbundle/tree * Get the TextMate bundle at https://github.com/visionmedia/jspec.tmbundle/tree
@ -694,7 +793,9 @@ JSpec more enjoyable, and bug free ;)
* Lawrence Pit * Lawrence Pit
* mpd@jesters-court.ne * mpd@jesters-court.ne
* kevin.gisi@gmail.com * kevin.gisi@gmail.com
* tony_t_tubbs@yahoo.com
* enno84@gmx.net * enno84@gmx.net
* fnando
== License == License

View File

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

View File

@ -2,16 +2,17 @@
JSPEC_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) JSPEC_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
$:.unshift JSPEC_ROOT $:.unshift JSPEC_ROOT
require 'rubygems' require 'rubygems'
require 'commander' require 'commander/import'
require 'bind' require 'bind'
require 'fileutils' require 'fileutils'
require 'server/server'
RHINO = 'java org.mozilla.javascript.tools.shell.Main' RHINO = 'java org.mozilla.javascript.tools.shell.Main'
program :name, 'JSpec' program :name, 'JSpec'
program :version, '2.8.4' program :version, '2.11.7'
program :description, 'JavaScript BDD Testing Framework' program :description, 'JavaScript BDD Testing Framework'
default_command :bind default_command :bind
@ -20,17 +21,47 @@ command :init do |c|
c.summary = 'Initialize a JSpec project template' c.summary = 'Initialize a JSpec project template'
c.description = 'Initialize a JSpec project template. Defaults to the current directory c.description = 'Initialize a JSpec project template. Defaults to the current directory
when [dest] is not specified. The template includes several files for 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.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| c.when_called do |args, options|
dest = args.shift || '.' dest = args.shift || '.'
if options.rails if options.rails
initialize_rails_to dest initialize_rails_at dest, options
else else
initialize_to dest initialize_at dest, options
end 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
end end
@ -71,38 +102,44 @@ command :run do |c|
the [path] to spec/server.html' the [path] to spec/server.html'
c.example 'Run once in Safari', 'jspec run' 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 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 '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 '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 'Shortcut for the previous example', 'jspec --browsers Safari,Firefox'
c.example 'Auto-run rhino when a file is altered', 'jspec --rhino' 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.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 '-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 '-B', '--bind', 'Auto-run specs when source files or specs are altered'
c.option '-R', '--rhino', 'Run specs using Rhino' c.option '-R', '--rhino', 'Run specs using Rhino'
c.option '-S', '--server', 'Run specs using the JSpec server' 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| c.when_called do |args, options|
# Rails # Rails
if rails? if rails?
options.default :browsers => %w( Safari ), :paths => ['public/javascripts/**/*.js', 'jspec/**/*.js'] options.default :paths => ['public/javascripts/**/*.js', 'jspec/**/*.js'], :port => 4444
else else
options.default :browsers => %w( Safari ), :paths => ['lib/**/*.js', 'spec/**/*.js'] options.default :paths => ['lib/**/*.js', 'spec/**/*.js'], :port => 4444
end end
# Actions # Actions
if options.rhino if options.rhino
spec = args.shift || path_to('spec.rhino.js') suite = args.shift || path_to('spec.rhino.js')
action = lambda { rhino spec } action = lambda { rhino suite }
elsif options.server_only
start_server options, nil
elsif options.server elsif options.server
spec = args.shift || path_to('spec.server.html') raise 'Cannot use --server with --bind' if options.bind
action = lambda { start_server options, spec } suite = args.shift || path_to('spec.server.html')
action = lambda { start_server suite, options }
else else
spec = args.shift || path_to('spec.dom.html') suite = args.shift || path_to('spec.dom.html')
action = Bind::Actions::RefreshBrowsers.new spec, *options.browsers browsers = browsers_for options.browsers || ['safari']
action = lambda do
browsers.each do |browser|
browser.visit File.expand_path(suite)
end
end
end end
# Binding # Binding
@ -110,73 +147,159 @@ command :run do |c|
listener = Bind::Listener.new :paths => options.paths, :interval => 1, :actions => [action], :debug => $stdout listener = Bind::Listener.new :paths => options.paths, :interval => 1, :actions => [action], :debug => $stdout
listener.run! listener.run!
else else
action.call File.new(spec) action.call File.new(suite)
end end
end end
end end
alias_command :bind, :run, '--bind' alias_command :bind, :run, '--bind'
def initialize_to dest ##
# Initialize template at _dest_.
def initialize_at dest, options
unless Dir[dest + '/*'].empty? unless Dir[dest + '/*'].empty?
abort unless agree "'#{dest}' is not empty; continue? " abort unless agree "'#{dest}' is not empty; continue? "
end end
copy_template_to 'default', dest copy_template_to 'default', dest
setup_lib_dir dest, options
replace_root_in dest, 'spec/spec.dom.html', 'spec/spec.rhino.js' replace_root_in dest, 'spec/spec.dom.html', 'spec/spec.rhino.js'
end end
def initialize_rails_to dest ##
# Initialize rails template at _dest_.
def initialize_rails_at dest, options
unless looks_like_rails_root?(dest) unless looks_like_rails_root?(dest)
abort unless agree "'#{dest}' does not look like root of a rails project; continue? " abort unless agree "'#{dest}' does not look like root of a rails project; continue? "
end end
copy_template_to 'rails', "#{dest}/jspec" copy_template_to 'rails', "#{dest}/jspec"
setup_lib_dir "#{dest}/jspec", options
replace_root_in "#{dest}/jspec", 'spec.dom.html', 'spec.rhino.js' replace_root_in "#{dest}/jspec", 'spec.dom.html', 'spec.rhino.js'
end end
##
# Copy template _name_ to _dest_.
def copy_template_to name, dest def copy_template_to name, dest
FileUtils.mkdir_p dest FileUtils.mkdir_p dest
FileUtils.cp_r path_to_template(name), dest FileUtils.cp_r path_to_template(name), dest
end end
##
# Return path to template _name_.
def path_to_template name def path_to_template name
File.join JSPEC_ROOT, 'templates', name, '.' File.join JSPEC_ROOT, 'templates', name, '.'
end end
##
# Resolve path to _file_. Supports rails and unbound projects.
def path_to file def path_to file
rails? ? "jspec/#{file}" : "spec/#{file}" rails? ? "jspec/#{file}" : "spec/#{file}"
end end
##
# Execute _file_ with Rhino.
def rhino file def rhino file
abort "#{file} not found" unless File.exists? file raise "#{file} not found" unless File.exists? file
system "#{RHINO} #{file}" system "#{RHINO} #{file}"
end end
def start_server options, spec ##
require 'server/server' # Start server with _suite_ html and _options_.
JSpec::Server.start options, spec
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 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? def rails?
File.directory? 'jspec' File.directory? 'jspec'
end end
##
# Replace JSPEC_ROOT placeholder in _paths_ relative to _dest_.
def replace_root_in dest, *paths 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| paths.each do |path|
path = File.join dest, 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 } File.open(path, 'w') { |file| file.write contents }
end end
end end
##
# Update JSpec version in _paths_. Matches jspec-TRIPLE
def update_version_in *paths def update_version_in *paths
paths.each do |path| paths.each do |path|
next unless File.exists? 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 } File.open(path, 'r+'){ |file| file.write contents }
say "Updated #{path}; #{$1} -> #{program(:version)}" say "Updated #{path}; #{$1} -> #{program(:version)}"
end end
say "Finished updating JSpec" say "Finished updating JSpec"
end end
##
# Check if _path_ looks like a rails root directory.
def looks_like_rails_root? path = '.' def looks_like_rails_root? path = '.'
File.directory? "#{path}/vendor" File.directory? "#{path}/vendor"
end 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| Gem::Specification.new do |s|
s.name = %q{jspec} 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.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
s.authors = ["TJ Holowaychuk"] s.authors = ["TJ Holowaychuk"]
s.date = %q{2009-08-02} s.date = %q{2009-10-15}
s.default_executable = %q{jspec} s.default_executable = %q{jspec}
s.description = %q{JavaScript BDD Testing Framework} s.description = %q{JavaScript BDD Testing Framework}
s.email = %q{tj@vision-media.ca} s.email = %q{tj@vision-media.ca}
s.executables = ["jspec"] 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.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 = ["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.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.homepage = %q{http://visionmedia.github.com/jspec}
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Jspec", "--main", "README.rdoc"] s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Jspec", "--main", "README.rdoc"]
s.require_paths = ["lib"] s.require_paths = ["lib"]
@ -25,14 +25,20 @@ Gem::Specification.new do |s|
s.specification_version = 3 s.specification_version = 3
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then 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<sinatra>, [">= 0"])
s.add_runtime_dependency(%q<visionmedia-bind>, [">= 0.2.6"]) 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 else
s.add_dependency(%q<visionmedia-commander>, [">= 3.2.9"]) s.add_dependency(%q<sinatra>, [">= 0"])
s.add_dependency(%q<visionmedia-bind>, [">= 0.2.6"]) 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
else else
s.add_dependency(%q<visionmedia-commander>, [">= 3.2.9"]) s.add_dependency(%q<sinatra>, [">= 0"])
s.add_dependency(%q<visionmedia-bind>, [">= 0.2.6"]) 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
end end

View File

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

View File

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

View File

@ -5,7 +5,7 @@
JSpec = { JSpec = {
version : '2.8.4', version : '2.11.7',
cache : {}, cache : {},
suites : [], suites : [],
modules : [], modules : [],
@ -13,8 +13,8 @@
matchers : {}, matchers : {},
stubbed : [], stubbed : [],
request : 'XMLHttpRequest' in this ? XMLHttpRequest : null, request : 'XMLHttpRequest' in this ? XMLHttpRequest : null,
stats : { specs : 0, assertions : 0, failures : 0, passes : 0, specsFinished : 0, suitesFinished : 0 }, stats : { specs: 0, assertions: 0, failures: 0, passes: 0, specsFinished: 0, suitesFinished: 0 },
options : { profile : false }, options : { profile: false },
/** /**
* Default context in which bodies are evaluated. * Default context in which bodies are evaluated.
@ -81,6 +81,46 @@
// --- Objects // --- Objects
formatters : { 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. * Default formatter, outputting to the DOM.
@ -98,48 +138,33 @@
var failuresOnly = option('failuresOnly') var failuresOnly = option('failuresOnly')
var classes = results.stats.failures ? 'has-failures' : '' var classes = results.stats.failures ? 'has-failures' : ''
if (!report) throw 'JSpec requires the element #' + id + ' to output its reports' 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. return JSpec.
escape(JSpec.contentsOf(body)). escape(JSpec.contentsOf(body)).
replace(/^ */gm, function(a){ return (new Array(Math.round(a.length / 3))).join(' ') }). 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 var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
if (displaySuite && suite.hasSpecs()) { if (displaySuite && suite.hasSpecs())
markup += '<tr class="description"><td colspan="2">' + escape(suite.description) + '</td></tr>' return '<tr class="description"><td colspan="2">' + escape(suite.description) + '</td></tr>' +
each(suite.specs, function(i, spec){ map(suite.specs, function(i, spec) {
markup += '<tr class="' + (i % 2 ? 'odd' : 'even') + '">' return '<tr class="' + (i % 2 ? 'odd' : 'even') + '">' +
if (spec.requiresImplementation()) (spec.requiresImplementation() ?
markup += '<td class="requires-implementation" colspan="2">' + escape(spec.description) + '</td>' '<td class="requires-implementation" colspan="2">' + escape(spec.description) + '</td>' :
else if (spec.passed() && !failuresOnly) (spec.passed() && !failuresOnly) ?
markup += '<td class="pass">' + escape(spec.description)+ '</td><td>' + spec.assertionsGraph() + '</td>' '<td class="pass">' + escape(spec.description)+ '</td><td>' + spec.assertionsGraph() + '</td>' :
else if(!spec.passed()) !spec.passed() ?
markup += '<td class="fail">' + escape(spec.description) + ' <em>' + spec.failure().message + '</em>' + '</td><td>' + spec.assertionsGraph() + '</td>' '<td class="fail">' + escape(spec.description) + ' <em>' + escape(spec.failure().message) + '</em>' + '</td><td>' + spec.assertionsGraph() + '</td>' :
markup += '<tr class="body"><td colspan="2"><pre>' + bodyContents(spec.body) + '</pre></td></tr>' '') +
}) '<tr class="body"><td colspan="2"><pre>' + bodyContents(spec.body) + '</pre></td></tr>'
markup += '</tr>' }).join('') + '</tr>'
} }).join('') + '</table></div>'
}
renderSuites = function(suites) {
each(suites, function(suite){
renderSuite(suite)
if (suite.hasSuites()) renderSuites(suite.suites)
})
}
renderSuites(results.suites)
markup += '</table></div>'
report.innerHTML = markup
}, },
/** /**
@ -153,42 +178,33 @@
print(color("\n Passes: ", 'bold') + color(results.stats.passes, 'green') + print(color("\n Passes: ", 'bold') + color(results.stats.passes, 'green') +
color(" Failures: ", 'bold') + color(results.stats.failures, 'red') + "\n") color(" Failures: ", 'bold') + color(results.stats.failures, 'red') + "\n")
indent = function(string) { function indent(string) {
return string.replace(/^(.)/gm, ' $1') return string.replace(/^(.)/gm, ' $1')
} }
renderSuite = function(suite) { each(results.allSuites, function(suite) {
displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
if (displaySuite && suite.hasSpecs()) { if (displaySuite && suite.hasSpecs()) {
print(color(' ' + suite.description, 'bold')) print(color(' ' + suite.description, 'bold'))
each(suite.specs, function(spec){ each(suite.specs, function(spec){
var assertionsGraph = inject(spec.assertions, '', function(graph, assertion){ var assertionsGraph = inject(spec.assertions, '', function(graph, assertion){
return graph + color('.', assertion.passed ? 'green' : 'red') return graph + color('.', assertion.passed ? 'green' : 'red')
}) })
if (spec.requiresImplementation()) if (spec.requiresImplementation())
print(color(' ' + spec.description, 'blue') + assertionsGraph) print(color(' ' + spec.description, 'blue') + assertionsGraph)
else if (spec.passed() && !failuresOnly) else if (spec.passed() && !failuresOnly)
print(color(' ' + spec.description, 'green') + assertionsGraph) print(color(' ' + spec.description, 'green') + assertionsGraph)
else if (!spec.passed()) else if (!spec.passed())
print(color(' ' + spec.description, 'red') + assertionsGraph + print(color(' ' + spec.description, 'red') + assertionsGraph +
"\n" + indent(spec.failure().message) + "\n") "\n" + indent(spec.failure().message) + "\n")
}) })
print("") print("")
} }
} })
renderSuites = function(suites) {
each(suites, function(suite){
renderSuite(suite)
if (suite.hasSuites()) renderSuites(suite.suites)
})
}
renderSuites(results.suites)
}, },
/** /**
* Console formatter, tested with Firebug and Safari 4. * Console formatter.
* *
* @api public * @api public
*/ */
@ -196,8 +212,7 @@
Console : function(results, options) { Console : function(results, options) {
console.log('') console.log('')
console.log('Passes: ' + results.stats.passes + ' Failures: ' + results.stats.failures) console.log('Passes: ' + results.stats.passes + ' Failures: ' + results.stats.failures)
each(results.allSuites, function(suite) {
renderSuite = function(suite) {
if (suite.ran) { if (suite.ran) {
console.group(suite.description) console.group(suite.description)
each(suite.specs, function(spec){ each(suite.specs, function(spec){
@ -210,28 +225,19 @@
console.error(assertionCount + ' ' + spec.description + ', ' + spec.failure().message) console.error(assertionCount + ' ' + spec.description + ', ' + spec.failure().message)
}) })
console.groupEnd() 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) { Assertion : function(matcher, actual, expected, negate) {
extend(this, { extend(this, {
message : '', message: '',
passed : false, passed: false,
actual : actual, actual: actual,
negate : negate, negate: negate,
matcher : matcher, matcher: matcher,
expected : expected, expected: expected,
// Report assertion results // Report assertion results
@ -269,18 +275,18 @@
// Times // Times
this.times = { this.times = {
'once' : 1, once : 1,
'twice' : 2 twice : 2
}[times] || times || 1 }[times] || times || 1
extend(this, { extend(this, {
calls : [], calls: [],
message : '', message: '',
defer : true, defer: true,
passed : false, passed: false,
negate : negate, negate: negate,
object : object, object: object,
method : method, method: method,
// Proxy return value // Proxy return value
@ -370,7 +376,7 @@
// Report assertion results // Report assertion results
report : function() { report : function() {
this.passed ? JSpec.stats.passes++ : JSpec.stats.failures++ this.passed ? ++JSpec.stats.passes : ++JSpec.stats.failures
return this return this
}, },
@ -380,7 +386,7 @@
var methodString = 'expected ' + object.toString() + '.' + method + '()' + (negate ? ' not' : '' ) var methodString = 'expected ' + object.toString() + '.' + method + '()' + (negate ? ' not' : '' )
function times(n) { 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())) if (this.expectedResult != null && (negate ? this.anyResultsPass() : this.anyResultsFail()))
@ -488,21 +494,21 @@
Spec : function(description, body) { Spec : function(description, body) {
extend(this, { extend(this, {
body : body, body: body,
description : description, description: description,
assertions : [], assertions: [],
// Add passing assertion // Add passing assertion
pass : function(message) { pass : function(message) {
this.assertions.push({ passed : true, message : message }) this.assertions.push({ passed: true, message: message })
++JSpec.stats.passes ++JSpec.stats.passes
}, },
// Add failing assertion // Add failing assertion
fail : function(message) { fail : function(message) {
this.assertions.push({ passed : false, message : message }) this.assertions.push({ passed: false, message: message })
++JSpec.stats.failures ++JSpec.stats.failures
}, },
@ -556,6 +562,63 @@
extend(this, methods) 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
DSLs : { DSLs : {
@ -623,6 +686,7 @@
if ('init' in module) module.init() if ('init' in module) module.init()
if ('utilities' in module) extend(this.defaultContext, module.utilities) if ('utilities' in module) extend(this.defaultContext, module.utilities)
if ('matchers' in module) this.addMatchers(module.matchers) if ('matchers' in module) this.addMatchers(module.matchers)
if ('formatters' in module) extend(this.formatters, module.formatters)
if ('DSLs' in module) if ('DSLs' in module)
each(module.DSLs, function(name, methods){ each(module.DSLs, function(name, methods){
JSpec.DSLs[name] = JSpec.DSLs[name] || {} JSpec.DSLs[name] = JSpec.DSLs[name] || {}
@ -662,14 +726,9 @@
*/ */
evalHook : function(module, name, args) { evalHook : function(module, name, args) {
var context = this.context || this.defaultContext hook('evaluatingHookBody', module, name)
var contents = this.contentsOf(module[name]) try { return module[name].apply(module, args) }
var params = this.paramsFor(module[name]) catch(e) { error('Error in hook ' + module.name + '.' + name + ': ', e) }
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) }
}, },
/** /**
@ -820,10 +879,10 @@
case String: case String:
if (captures = body.match(/^alias (\w+)/)) return JSpec.matchers[last(captures)] if (captures = body.match(/^alias (\w+)/)) return JSpec.matchers[last(captures)]
if (body.length < 4) body = 'actual ' + body + ' expected' 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: case Function:
return { match : body } return { match: body }
default: default:
return body return body
@ -856,7 +915,7 @@
hash : function(object) { hash : function(object) {
if (object == null) return 'null' if (object == null) return 'null'
if (object == undefined) return 'undefined' if (object == undefined) return 'undefined'
serialize = function(prefix) { function serialize(prefix) {
return inject(object, prefix + ':', function(buffer, key, value){ return inject(object, prefix + ':', function(buffer, key, value){
return buffer += hash(value) return buffer += hash(value)
}) })
@ -905,13 +964,13 @@
if (object === false) return 'false' if (object === false) return 'false'
if (object.an_instance_of) return 'an instance of ' + object.an_instance_of.name 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 && object.selector.length > 0) return 'selector ' + puts(object.selector) + ''
if (object.jquery) return escape(object.html()) if (object.jquery) return object.html()
if (object.nodeName) return escape(object.outerHTML) if (object.nodeName) return object.outerHTML
switch (object.constructor) { switch (object.constructor) {
case String: return "'" + escape(object) + "'" case String: return "'" + object + "'"
case Number: return object case Number: return object
case Function: return object.name || object case Function: return object.name || object
case Array : case Array:
return inject(object, '[', function(b, v){ return inject(object, '[', function(b, v){
return b + ', ' + puts(v) return b + ', ' + puts(v)
}).replace('[,', '[') + ' ]' }).replace('[,', '[') + ' ]'
@ -920,7 +979,7 @@
return b + ', ' + puts(k) + ' : ' + puts(v) return b + ', ' + puts(k) + ' : ' + puts(v)
}).replace('{,', '{') + ' }' }).replace('{,', '{') + ' }'
default: default:
return escape(object.toString()) return object.toString()
} }
}, },
@ -933,11 +992,11 @@
*/ */
escape : function(html) { escape : function(html) {
return html.toString(). return html.toString()
replace(/&/gmi, '&amp;'). .replace(/&/gmi, '&amp;')
replace(/"/gmi, '&quot;'). .replace(/"/gmi, '&quot;')
replace(/>/gmi, '&gt;'). .replace(/>/gmi, '&gt;')
replace(/</gmi, '&lt;') .replace(/</gmi, '&lt;')
}, },
/** /**
@ -1247,18 +1306,6 @@
contentsOf : function(body) { contentsOf : function(body) {
return body.toString().match(/^[^\{]*{((.*\n*)*)}/m)[1] 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. * Evaluate a JSpec capture body.
@ -1288,19 +1335,22 @@
*/ */
preprocess : function(input) { preprocess : function(input) {
if (typeof input != 'string') return
input = hookImmutable('preprocessing', input) input = hookImmutable('preprocessing', input)
return input. return input.
replace(/\r\n/gm, '\n').
replace(/([\w\.]+)\.(stub|destub)\((.*?)\)$/gm, '$2($1, $3)'). replace(/([\w\.]+)\.(stub|destub)\((.*?)\)$/gm, '$2($1, $3)').
replace(/describe\s+(.*?)$/gm, 'describe($1, function(){'). replace(/describe\s+(.*?)$/gm, 'describe($1, function(){').
replace(/^\s+it\s+(.*?)$/gm, ' it($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(/^\s*end(?=\s|$)/gm, '});').
replace(/-\{/g, 'function(){'). replace(/-\{/g, 'function(){').
replace(/(\d+)\.\.(\d+)/g, function(_, a, b){ return range(a, b) }). replace(/(\d+)\.\.(\d+)/g, function(_, a, b){ return range(a, b) }).
replace(/\.should([_\.]not)?[_\.](\w+)(?: |;|$)(.*)$/gm, '.should$1_$2($3)'). replace(/\.should([_\.]not)?[_\.](\w+)(?: |;|$)(.*)$/gm, '.should$1_$2($3)').
replace(/([\/\s]*)(.+?)\.(should(?:[_\.]not)?)[_\.](\w+)\((.*)\)\s*;?$/gm, '$1 expect($2).$3($4, $5)'). replace(/([\/\s]*)(.+?)\.(should(?:[_\.]not)?)[_\.](\w+)\((.*)\)\s*;?$/gm, '$1 expect($2).$3($4, $5)').
replace(/, \)/gm, ')'). 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. * Ad-hoc POST request for JSpec server usage.
* *
* @param {string} url * @param {string} uri
* @param {string} data * @param {string} data
* @api private * @api private
*/ */
post : function(url, data) { post : function(uri, data) {
if (any(hook('posting', url, data), haveStopped)) return if (any(hook('posting', uri, data), haveStopped)) return
var request = this.xhr() var request = this.xhr()
request.open('POST', url, false) request.open('POST', uri, false)
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') request.setRequestHeader('Content-Type', 'application/json')
request.send(data) 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. * 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} * @return {XMLHttpRequest, ActiveXObject}
* @api private * @api private
*/ */
xhr : function() { 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() var request = this.xhr()
request.open('GET', file, false) request.open('GET', file, false)
request.send(null) 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 else
error("failed to load `" + file + "'") 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 = { MockXMLHttpRequest.prototype = {
status : 0, status: 0,
async : true, async: true,
readyState : 0, readyState: 0,
responseText : '', responseText: '',
abort : function(){}, abort: function(){},
onreadystatechange : function(){}, onreadystatechange: function(){},
/** /**
* Return response headers hash. * Return response headers hash.
@ -77,47 +77,47 @@
// --- Response status codes // --- Response status codes
JSpec.statusCodes = { JSpec.statusCodes = {
100 : 'Continue', 100: 'Continue',
101 : 'Switching Protocols', 101: 'Switching Protocols',
200 : 'OK', 200: 'OK',
201 : 'Created', 201: 'Created',
202 : 'Accepted', 202: 'Accepted',
203 : 'Non-Authoritative Information', 203: 'Non-Authoritative Information',
204 : 'No Content', 204: 'No Content',
205 : 'Reset Content', 205: 'Reset Content',
206 : 'Partial Content', 206: 'Partial Content',
300 : 'Multiple Choice', 300: 'Multiple Choice',
301 : 'Moved Permanently', 301: 'Moved Permanently',
302 : 'Found', 302: 'Found',
303 : 'See Other', 303: 'See Other',
304 : 'Not Modified', 304: 'Not Modified',
305 : 'Use Proxy', 305: 'Use Proxy',
307 : 'Temporary Redirect', 307: 'Temporary Redirect',
400 : 'Bad Request', 400: 'Bad Request',
401 : 'Unauthorized', 401: 'Unauthorized',
402 : 'Payment Required', 402: 'Payment Required',
403 : 'Forbidden', 403: 'Forbidden',
404 : 'Not Found', 404: 'Not Found',
405 : 'Method Not Allowed', 405: 'Method Not Allowed',
406 : 'Not Acceptable', 406: 'Not Acceptable',
407 : 'Proxy Authentication Required', 407: 'Proxy Authentication Required',
408 : 'Request Timeout', 408: 'Request Timeout',
409 : 'Conflict', 409: 'Conflict',
410 : 'Gone', 410: 'Gone',
411 : 'Length Required', 411: 'Length Required',
412 : 'Precondition Failed', 412: 'Precondition Failed',
413 : 'Request Entity Too Large', 413: 'Request Entity Too Large',
414 : 'Request-URI Too Long', 414: 'Request-URI Too Long',
415 : 'Unsupported Media Type', 415: 'Unsupported Media Type',
416 : 'Requested Range Not Satisfiable', 416: 'Requested Range Not Satisfiable',
417 : 'Expectation Failed', 417: 'Expectation Failed',
422 : 'Unprocessable Entity', 422: 'Unprocessable Entity',
500 : 'Internal Server Error', 500: 'Internal Server Error',
501 : 'Not Implemented', 501: 'Not Implemented',
502 : 'Bad Gateway', 502: 'Bad Gateway',
503 : 'Service Unavailable', 503: 'Service Unavailable',
504 : 'Gateway Timeout', 504: 'Gateway Timeout',
505 : 'HTTP Version Not Supported' 505: 'HTTP Version Not Supported'
} }
/** /**
@ -136,10 +136,10 @@
headers = headers || {} headers = headers || {}
headers['content-type'] = type headers['content-type'] = type
JSpec.extend(XMLHttpRequest.prototype, { JSpec.extend(XMLHttpRequest.prototype, {
responseText : body, responseText: body,
responseHeaders : headers, responseHeaders: headers,
status : status, status: status,
statusText : JSpec.statusCodes[status] statusText: JSpec.statusCodes[status]
}) })
}} }}
} }
@ -155,18 +155,28 @@
} }
JSpec.include({ JSpec.include({
name: 'Mock XHR',
// --- Utilities // --- Utilities
utilities : { utilities : {
mockRequest : mockRequest, mockRequest: mockRequest,
unmockRequest : unmockRequest unmockRequest: unmockRequest
}, },
// --- Hooks // --- Hooks
afterSpec : function() { afterSpec : function() {
this.utilities.unmockRequest() unmockRequest()
},
// --- DSLs
DSLs : {
snake : {
mock_request: mockRequest,
unmock_request: unmockRequest
}
} }
}) })

View File

@ -1,16 +1,228 @@
module JSpec require 'rbconfig'
class Browser
def open url #--
`open -g -a #{name} #{url}` # Browser
end #++
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 # Subclasses.
end
def self.subclasses
class Firefox < self; end @subclasses ||= []
class Safari < self; end
class Opera < self; end
end 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' $:.unshift File.dirname(__FILE__)
require 'rack'
require 'server/browsers' require 'sinatra'
require 'thread'
require 'browsers'
require 'helpers'
require 'routes'
module JSpec module JSpec
class Server class Server
attr_reader :responses, :browsers, :root
def initialize options = {} ##
@responses = [] # Suite HTML.
@browsers = options.delete :browsers
@root = options.delete :root 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 end
def call env ##
request = Rack::Request.new env # URI formed by the given host and port.
path = request.path_info
body = case path def uri
when '/' 'http://%s:%d' % [host, port]
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"
end end
def when_finished &block ##
Thread.new { # Start the server with _browsers_ which defaults to all supported browsers.
sleep 0.1 while responses.length < browsers.length
yield
}
end
def self.start options, spec def start browsers = nil
app = Rack::Builder.new do browsers ||= Browser.subclasses.map { |browser| browser.new }
server = JSpec::Server.new :browsers => options.browsers, :root => '.' browsers.map do |browser|
server.when_finished { exit } Thread.new {
run server sleep 1
end if browser.supported?
unless options.server_only browser.setup
Thread.new { browser.visit uri + '/' + suite
sleep 2 browser.teardown
puts "Running browsers: #{options.browsers.join(', ')}\n\n" end
run_browsers options.browsers, spec
} }
end end.push(Thread.new {
puts "JSpec server started\n" start!
Rack::Handler::Mongrel.run app, :Port => 4444 }).reverse.each { |thread| thread.join }
self
end end
def self.run_browsers browsers, spec private
browsers.each do |name|
browser(name).open "http://localhost:4444/#{spec}" #: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
end end
def self.browser name
eval("JSpec::Browser::#{name}").new
end
end 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.js"></script>
<script src="../lib/jspec.jquery.js"></script> <script src="../lib/jspec.jquery.js"></script>
<script src="../lib/jspec.xhr.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 src="spec.grammar-less.js"></script>
<script> <script>
function runSuites() { function runSuites() {
@ -20,7 +21,7 @@
.exec('spec.modules.js') .exec('spec.modules.js')
.exec('spec.xhr.js') .exec('spec.xhr.js')
.exec('spec.jquery.xhr.js') .exec('spec.jquery.xhr.js')
.run() .run({ failuresOnly: true })
.report() .report()
} }
</script> </script>

View File

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

View File

@ -20,16 +20,16 @@ describe 'jQuery'
describe 'async' describe 'async'
it 'should load mah cookies (textfile)' it 'should load mah cookies (textfile)'
$.post('async', function(text){ $.get('async', function(text){
text.should_eql 'cookies!' text.should_eql 'cookies!'
}) })
end end
it 'should load mah cookies twice (ensure multiple async requests work)' it 'should load mah cookies twice (ensure multiple async requests work)'
$.post('async', function(text){ $.get('async', function(text){
text.should.eql 'cookies!' text.should.eql 'cookies!'
}) })
$.post('async', function(text){ $.get('async', function(text){
text.should.not.eql 'rawr' text.should.not.eql 'rawr'
}) })
end end
@ -49,7 +49,10 @@ describe 'jQuery'
end end
it 'should fail with pretty print of element' 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 end
describe 'have_tag / have_one' describe 'have_tag / have_one'

View File

@ -1,5 +1,35 @@
describe 'jQuery' 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()' describe '.getJSON()'
it 'should work with mockRequest' it 'should work with mockRequest'
mockRequest().and_return('{ foo : "bar" }') mockRequest().and_return('{ foo : "bar" }')

View File

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

View File

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

View File

@ -19,22 +19,6 @@ describe 'JSpec'
it 'should run afterSuite' it 'should run afterSuite'
addedAfterSuite.should.be_true addedAfterSuite.should.be_true
end 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 end
describe '.hook()' describe '.hook()'

View File

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

View File

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

View File

@ -2,7 +2,10 @@
describe 'Utility' describe 'Utility'
describe 'fail()' describe 'fail()'
it 'should fail the current spec' 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
end end
@ -256,21 +259,4 @@ describe 'Utility'
end end
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 end

View File

@ -1,6 +1,6 @@
describe 'JSpec' describe 'JSpec'
describe '.mockRequest' describe 'Mock XHR'
before before
responseFrom = function(path) { responseFrom = function(path) {
request = new XMLHttpRequest request = new XMLHttpRequest
@ -10,6 +10,11 @@ describe 'JSpec'
} }
end 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' it 'should mock XMLHttpRequests if unmockRequest() is called or the spec block has finished'
original = XMLHttpRequest original = XMLHttpRequest
mockRequest().and_return('test') 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 JSpec
.exec('spec/spec.core.js') .exec('spec/spec.core.js')
.run({ formatter : JSpec.formatters.Terminal }) .run({ formatter: JSpec.formatters.Terminal })
.report() .report()

View File

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

View File

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