oojspec 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.md +174 -0
- data/Rakefile +27 -0
- data/app/views/oojspec/runner.html.erb +25 -0
- data/lib/assets/javascripts/oojspec.js.coffee +256 -0
- data/lib/assets/stylesheets/oojspec.css.erb +5 -0
- data/lib/oojspec/engine.rb +15 -0
- data/lib/oojspec/version.rb +3 -0
- data/lib/oojspec.rb +5 -0
- data/lib/tasks/oojspec_tasks.rake +4 -0
- data/vendor/assets/images/buster/logo.png +0 -0
- data/vendor/assets/javascripts/buster/all.js.coffee +3 -0
- data/vendor/assets/javascripts/buster/buster-assertions.js +782 -0
- data/vendor/assets/javascripts/buster/buster-core.js +222 -0
- data/vendor/assets/javascripts/buster/buster-event-emitter.js +152 -0
- data/vendor/assets/javascripts/buster/buster-format.js +199 -0
- data/vendor/assets/javascripts/buster/expect.js +63 -0
- data/vendor/assets/javascripts/buster/html.js +324 -0
- data/vendor/assets/javascripts/buster/stack-filter.js +63 -0
- data/vendor/assets/stylesheets/buster/buster-test.css +201 -0
- metadata +104 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
# oojspec - Object-oriented client-side testing
|
2
|
+
|
3
|
+
`oojspec` is a test runner built on top of [Buster.js](http://busterjs.org/), focused on
|
4
|
+
integration tests. It is also a Rails engine, although you can use it in non-Rails
|
5
|
+
applications - more on that in a later topic.
|
6
|
+
|
7
|
+
It uses the same assertions and reporter and you can choose between expectations
|
8
|
+
and assertions style on your examples or even mix them up.
|
9
|
+
|
10
|
+
This is heavily inspired by another beloved tool called [RSpec](https://www.relishapp.com/rspec/),
|
11
|
+
and also takes some inspiration from Jasmine and Buster.
|
12
|
+
|
13
|
+
I really prefer writing expectations instead of assertions because I think it reads better. But
|
14
|
+
on the other hand I find expectations an overkill for things like:
|
15
|
+
|
16
|
+
```javascript
|
17
|
+
expect(seats.length).toBeTruthy()
|
18
|
+
```
|
19
|
+
|
20
|
+
I'd rather prefer to write:
|
21
|
+
|
22
|
+
```javascript
|
23
|
+
assert(seats.length)
|
24
|
+
```
|
25
|
+
|
26
|
+
# Is it production ready?
|
27
|
+
|
28
|
+
It should be, but its API is certainly going to change a lot before it becomes stable.
|
29
|
+
|
30
|
+
So I wouldn't advise you to write tons of tests with `oojspec` because you might have to rewrite
|
31
|
+
them in the future when the API changes.
|
32
|
+
|
33
|
+
On the other side, I'll be using it myself in my own projects, replacing my Jasmine specs.
|
34
|
+
|
35
|
+
# Where are its tests?
|
36
|
+
|
37
|
+
I'm still not sure on how to properly test it. For the time being, I'm writing some examples
|
38
|
+
in a separate project. I'll publish its link here as soon as I polish it.
|
39
|
+
|
40
|
+
# Why not just using Buster.js (or Jasmine.js)?
|
41
|
+
|
42
|
+
Jasmine.js doesn't support beforeAll/afterAll and that was the main motivation I started to develop
|
43
|
+
another test runner. But let's be honest, I didn't want to maintain all expectations/assertions by
|
44
|
+
myself. Also I didn't want to worry about the reporter. I just wanted to focus on the runner itself.
|
45
|
+
|
46
|
+
Mocha/Chai seemed a great alternative but I had to support older Internet Explorer in my
|
47
|
+
application while the syntax of Chai required a feature not supported by those browsers.
|
48
|
+
|
49
|
+
Then I've heard about this excellent testing tool called Buster.js. It supported beforeAll/afterAll
|
50
|
+
just like Mocha but it didn't give any guarantees about the order of execution of the tests and
|
51
|
+
I needed that guarantee for some integration tests.
|
52
|
+
|
53
|
+
But on the other side it is pretty modular and I would be able to take advantage of their
|
54
|
+
assertions and expectations syntax as well as its reporter (although I'm still not happy with
|
55
|
+
the default html reporter and I'll probably have to write a new one in the future).
|
56
|
+
|
57
|
+
As Buster.js allowed me to focus on the runner itself I decided to begin this new project.
|
58
|
+
|
59
|
+
Also I took the chance to do things the correct way in my opinion. I don't like the fact that most
|
60
|
+
test runners (all of them?) will publish `describe`, `it`, `waitsFor` etc. independently from the
|
61
|
+
context.
|
62
|
+
|
63
|
+
In contrast `oojspec` will only export `describe`. The other allowed features will be available
|
64
|
+
depending on the context. Inside a description `example`, `specify`, `it`, `xit`, `pending` and
|
65
|
+
`describe` will be available. Inside an example `expect`, `assert`, `waitsFor` and `runs` will be
|
66
|
+
available .
|
67
|
+
|
68
|
+
# CoffeeScript?! Really?!
|
69
|
+
|
70
|
+
JavaScript?! Really?!
|
71
|
+
|
72
|
+
This is me who is writing this runner and I dislike JavaScript, so please keep your preferences
|
73
|
+
for you. I won't change to pure JavaScript. Period.
|
74
|
+
|
75
|
+
# Can I test my JavaScript code with oojspec despite it being written in CS?
|
76
|
+
|
77
|
+
Of course. Why are you asking me that?
|
78
|
+
|
79
|
+
On the other hand I'd advise you to write your examples in CoffeeScript for brevity.
|
80
|
+
|
81
|
+
JavaScript example:
|
82
|
+
|
83
|
+
```javascript
|
84
|
+
oojspec.describe('Some description', function(){
|
85
|
+
this.example('Some example', function(){
|
86
|
+
this.assert(true)
|
87
|
+
})
|
88
|
+
});
|
89
|
+
```
|
90
|
+
|
91
|
+
CoffeeScript version:
|
92
|
+
|
93
|
+
```coffeescript
|
94
|
+
oojspec.describe 'Some description', ->
|
95
|
+
@example 'Some example', -> @assert true
|
96
|
+
```
|
97
|
+
|
98
|
+
Alternatively, you can use some shortcut to `this` if you're using JavaScript:
|
99
|
+
|
100
|
+
```javascript
|
101
|
+
oojspec.describe('Some description', function(s){
|
102
|
+
s.example('Some example', function(s){
|
103
|
+
s.assert(true)
|
104
|
+
})
|
105
|
+
});
|
106
|
+
```
|
107
|
+
|
108
|
+
But I'm not sure if this API will remain supported in the future although I don't currently have
|
109
|
+
any plans to change it.
|
110
|
+
|
111
|
+
# Usage with Rails
|
112
|
+
|
113
|
+
`oojspec` is also a Rails engine built on top of
|
114
|
+
[rails-sandbox-assets](https://github.com/rosenfeld/rails-sandbox-assets).
|
115
|
+
|
116
|
+
It takes advantage of the Rails asset pipeline to run your specs. To launch the runner, after
|
117
|
+
including `oojspec` to your Gemfile, run `rake sandbox_assets:serve`.
|
118
|
+
|
119
|
+
It will load all your specs inside `(spec|test)/javascripts/oojspec/` named `*_spec.js[.coffee]`
|
120
|
+
or `*_test.js[.coffee]`. Then run the examples by accessing http://localhost:5000/oojspec.
|
121
|
+
|
122
|
+
If you want to put your specs directly on `spec/javascripts`, add this to your application.rb:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
config.sandbox_assets.template = 'oojspec/runner'
|
126
|
+
```
|
127
|
+
|
128
|
+
Then you'll be able to run the specs by directly accessing http://localhost:5000.
|
129
|
+
|
130
|
+
By default this gem will expose `oojspec.describe` to the `window` object so that you can write it
|
131
|
+
directly from the top-level, but you can disable this exposition if you prefer:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
config.sandbox_assets.options[:skipt_oojspec_expose] = true
|
135
|
+
```
|
136
|
+
|
137
|
+
# What about non-Rails applications?
|
138
|
+
|
139
|
+
There are two ways you can use oojspec with non-Rails applications.
|
140
|
+
|
141
|
+
If you want to take advantage of the Rails asset pipeline,
|
142
|
+
[here](https://github.com/rosenfeld/jasmine_assets_enabler/tree/oojs)
|
143
|
+
is an example on how to integrate the `rails-sandbox-assets` gem (and consequently this one) to
|
144
|
+
your non-Rails application.
|
145
|
+
|
146
|
+
It is target to the `oojs` gem that is currently built on top of `rails_sandbox_jasmine` but this
|
147
|
+
is going to change and `oojs` will be built on top of `oojspec` in the future. But you can currently
|
148
|
+
just add both gems right now and ignore the Jasmine runner while I don't change `oojs`.
|
149
|
+
|
150
|
+
The other approach is to compile the source (possibly in the
|
151
|
+
[Try CoffeeScript page](http://coffeescript.org/)) and write your own custom runner HTML. Just take
|
152
|
+
the template provided by this gem as an example on how to write it.
|
153
|
+
|
154
|
+
I hope to have more free time to write more in-depth examples on how to do that in the Wiki
|
155
|
+
in the future.
|
156
|
+
|
157
|
+
# Plans for the future
|
158
|
+
|
159
|
+
There are so many but I'm not sure how long it will take to implement all of the intended
|
160
|
+
features in my spare time.
|
161
|
+
|
162
|
+
I'd like to support `given-and-when-and-then-and` style specs at some point.
|
163
|
+
|
164
|
+
Also, I'm still thinking about a more object-oriented API for the description blocks.
|
165
|
+
|
166
|
+
But I wanted to have some initial working version published soon before someone register an
|
167
|
+
`oojspec` gem before me! :)
|
168
|
+
|
169
|
+
# Contributing
|
170
|
+
|
171
|
+
I'd love to hear your opinions on the API and design of `oojspec` and of course contributions
|
172
|
+
will be very welcome if they're aligned with this project goals.
|
173
|
+
|
174
|
+
Enjoy! :)
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'Oojspec'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
5
|
+
|
6
|
+
<title>Buster.js Test Runner</title>
|
7
|
+
|
8
|
+
<%= stylesheet_link_tag "oojspec" %>
|
9
|
+
<%= javascript_include_tag "oojspec" %>
|
10
|
+
<%= javascript_tag "oojspec.exposeAll()" unless Rails.configuration.sandbox_assets.options[:skip_oojspec_expose] %>
|
11
|
+
|
12
|
+
<% @stylesheets.each do |css| -%>
|
13
|
+
<%= stylesheet_link_tag css %>
|
14
|
+
<% end -%>
|
15
|
+
|
16
|
+
<% @tests.each do |test| -%>
|
17
|
+
<%= javascript_include_tag test %>
|
18
|
+
<% end -%>
|
19
|
+
</head>
|
20
|
+
<body>
|
21
|
+
|
22
|
+
<%= javascript_tag "oojspec.autorun()" %>
|
23
|
+
</body>
|
24
|
+
</html>
|
25
|
+
|
@@ -0,0 +1,256 @@
|
|
1
|
+
# =require buster/all
|
2
|
+
|
3
|
+
window.oojspec = new class OojspecRunner
|
4
|
+
constructor: ->
|
5
|
+
@runner = buster.create buster.eventEmitter
|
6
|
+
@descriptions = []
|
7
|
+
@assertions = buster.assertions
|
8
|
+
(logFormatter = buster.create buster.format).quoteStrings = false
|
9
|
+
@assertions.format = buster.bind logFormatter, "ascii"
|
10
|
+
@assertions.on 'pass', => @stats.assertions++
|
11
|
+
@assertions.on 'failure', => @stats.failures++
|
12
|
+
#@runner.on 'context:start', => @stats.contexts++
|
13
|
+
@runner.on 'test:timeout', => @stats.timeouts++
|
14
|
+
@runner.on 'test:error', => @stats.errors++
|
15
|
+
@runner.on 'test:deferred', => @stats.deferred++
|
16
|
+
|
17
|
+
@stats =
|
18
|
+
contexts: 0
|
19
|
+
tests: 0
|
20
|
+
assertions: 0
|
21
|
+
errors: 0
|
22
|
+
failures: 0
|
23
|
+
timeouts: 0
|
24
|
+
deferred: 0
|
25
|
+
|
26
|
+
exposeAll: -> window.describe = @describe
|
27
|
+
autorun: -> @runSpecs() unless @disableAutorun
|
28
|
+
|
29
|
+
runSpecs: ->
|
30
|
+
@reporter = buster.reporters.html.create()
|
31
|
+
@reporter.listen @runner
|
32
|
+
@runner.emit 'suite:start', name: "Specs"
|
33
|
+
@runNextDescription()
|
34
|
+
|
35
|
+
runNextDescription: =>
|
36
|
+
(@runner.emit 'suite:end', @stats; return) unless @descriptions.length
|
37
|
+
# TODO: think about non null contexts usage
|
38
|
+
@descriptions.shift().run @runner, @assertions, null, @runNextDescription
|
39
|
+
|
40
|
+
describe: (description, block)=>
|
41
|
+
@stats.contexts++ # only root descriptions will be count
|
42
|
+
@descriptions.push new Description(description, block)
|
43
|
+
|
44
|
+
class Description
|
45
|
+
RESERVED = ['beforeAll', 'before', 'after', 'afterAll', 'describe', 'context',
|
46
|
+
'example', 'it', 'specify', 'pending', 'xit']
|
47
|
+
constructor: (@description, @block)->
|
48
|
+
|
49
|
+
run: (@runner, @assertions, @context, @onFinish, @beforeBlocks = [], @afterBlocks = [])->
|
50
|
+
@runner.emit 'context:start', name: @description
|
51
|
+
@dsl = new DescribeDsl
|
52
|
+
if c = @context
|
53
|
+
for reserved in RESERVED
|
54
|
+
try
|
55
|
+
throw new Error("'#{reserved}' method is reserved for oojspec usage only") if c[reserved]
|
56
|
+
catch e
|
57
|
+
e.name = "syntax error"
|
58
|
+
@runner.emit 'test:error', name: @description, error: e
|
59
|
+
@onDescriptionFinished(e)
|
60
|
+
c[reserved] = @dsl[reserved]
|
61
|
+
@runAround @beforeBlocks, @afterBlocks, @onDescriptionFinished, @processDescriptionBlock
|
62
|
+
|
63
|
+
onDescriptionFinished: (error)=>
|
64
|
+
if error and not error.handled
|
65
|
+
error.handled = true
|
66
|
+
@runner.emit 'test:error', { name: 'Error running describe statements', error }
|
67
|
+
@runner.emit 'context:end'
|
68
|
+
@onFinish error
|
69
|
+
|
70
|
+
runAround: (befores, afters, onFinish, block)->
|
71
|
+
new AroundBlock(befores, afters, block).run @runner, @assertions, @context, onFinish
|
72
|
+
|
73
|
+
processDescriptionBlock: (onFinish)=>
|
74
|
+
context = @context or @dsl
|
75
|
+
@block.call context, context
|
76
|
+
@runAround @dsl._beforeAllBlocks_, @dsl._afterAllBlocks_, onFinish, (@onExamplesFinished)=>
|
77
|
+
@runNextStep()
|
78
|
+
|
79
|
+
runNextStep: =>
|
80
|
+
(@onExamplesFinished(); return) unless @dsl._examples_.length
|
81
|
+
nextStep = @dsl._examples_.shift()
|
82
|
+
(@reportDeferred(nextStep.description); @runNextStep(); return) if nextStep.pending
|
83
|
+
nextTick =
|
84
|
+
if nextStep instanceof Description then => nextStep
|
85
|
+
.run @runner, @assertions, @context, @runNextStep, @dsl._beforeBlocks_, @dsl._afterBlocks_
|
86
|
+
else => # ExampleWithHooks
|
87
|
+
nextStep.run @runner, @assertions, @context, @onExampleFinished
|
88
|
+
setTimeout nextTick, 0
|
89
|
+
|
90
|
+
onExampleFinished: (error)=>
|
91
|
+
(@runNextStep(); return) unless error and not error.handled
|
92
|
+
error.handled = true
|
93
|
+
console.log error
|
94
|
+
name = @description
|
95
|
+
name += " in #{error.source}" if error.source
|
96
|
+
@runner.emit 'test:error', { name, error }
|
97
|
+
@onFinish(error)
|
98
|
+
|
99
|
+
reportDeferred: (description)-> @runner.emit 'test:deferred', name: description
|
100
|
+
|
101
|
+
class DescribeDsl
|
102
|
+
addHook = (description, block, container)->
|
103
|
+
if typeof description is 'string'
|
104
|
+
return unless block # pending hook
|
105
|
+
block.description = description
|
106
|
+
else
|
107
|
+
block = description
|
108
|
+
container.push block
|
109
|
+
|
110
|
+
constructor: ->
|
111
|
+
@_beforeAllBlocks_ = []
|
112
|
+
@_beforeBlocks_ = []
|
113
|
+
@_afterBlocks_ = []
|
114
|
+
@_afterAllBlocks_ = []
|
115
|
+
@_examples_ = []
|
116
|
+
# aliases:
|
117
|
+
@it = @specify = @example
|
118
|
+
@context = @describe
|
119
|
+
@xit = @pending
|
120
|
+
|
121
|
+
beforeAll: (description, block)=> addHook description, block, @_beforeAllBlocks_
|
122
|
+
before: (description, block)=> addHook description, block, @_beforeBlocks_
|
123
|
+
after: (description, block)=> addHook description, block, @_afterBlocks_
|
124
|
+
afterAll: (description, block)=> addHook description, block, @_afterAllBlocks_
|
125
|
+
describe: (description, block)=>
|
126
|
+
@_examples_.push new Description(description, block, @_beforeBlocks_, @_afterBlocks_)
|
127
|
+
example: (description, block)=>
|
128
|
+
@_examples_.push new ExampleWithHooks(description, @_beforeBlocks_, @_afterBlocks_, block)
|
129
|
+
pending: (description)=> @_examples_.push {description, pending: true}
|
130
|
+
|
131
|
+
class AroundBlock
|
132
|
+
constructor: (@beforeBlocks, @afterBlocks, @block)->
|
133
|
+
|
134
|
+
run: (@runner, @assertions, @context, @onFinish)->
|
135
|
+
@runGroup @beforeBlocks, ((e)=> @onBeforeError e), (wasSuccessful)=>
|
136
|
+
if wasSuccessful
|
137
|
+
@runMainBlock @block, (error)=>
|
138
|
+
@registerError error
|
139
|
+
@runAfterGroup()
|
140
|
+
else @runAfterGroup()
|
141
|
+
|
142
|
+
registerError: (error)->
|
143
|
+
@runner.emit 'oojspec:log:error', error
|
144
|
+
@error or= error
|
145
|
+
|
146
|
+
runMainBlock: (block, onFinish)->
|
147
|
+
try
|
148
|
+
block onFinish
|
149
|
+
catch error
|
150
|
+
error = new Error(error) if typeof error is 'string'
|
151
|
+
@registerError error
|
152
|
+
onFinish error
|
153
|
+
|
154
|
+
runGroup: (group, onError, onFinish)->
|
155
|
+
new ExampleGroupWithoutHooks(@assertions, @context, group, onFinish, onError).run()
|
156
|
+
|
157
|
+
onBeforeError: (error)-> error.source = "before hook"; @registerError error
|
158
|
+
onAfterError: (error)-> error.source = "after hook"; @registerError error
|
159
|
+
runAfterGroup: -> @runGroup @afterBlocks, ((e)=> @onAfterError e), (=> @onAfterHooks())
|
160
|
+
onAfterHooks: -> @onFinish @error
|
161
|
+
|
162
|
+
class ExampleWithHooks extends AroundBlock
|
163
|
+
constructor: (@description, @beforeBlocks, @afterBlocks, @block)->
|
164
|
+
runMainBlock: (block, onFinish)-> new Example(block).run @assertions, @context, onFinish
|
165
|
+
onAfterHooks: ->
|
166
|
+
@handleResult()
|
167
|
+
super
|
168
|
+
|
169
|
+
handleResult: ->
|
170
|
+
(@runner.emit 'test:success', name: @description; return) unless @error
|
171
|
+
@error.handled = true
|
172
|
+
if @error.name is 'AssertionError'
|
173
|
+
@runner.emit 'test:failure', name: @description, error: @error
|
174
|
+
return
|
175
|
+
|
176
|
+
if @error.timeout
|
177
|
+
@error.source or= 'example'
|
178
|
+
@runner.emit 'test:timeout', name: @description, error: @error
|
179
|
+
return
|
180
|
+
@error.name = 'Exception'
|
181
|
+
@error.name += " in #{@error.source}" if @error.source
|
182
|
+
@runner.emit 'test:error', name: @description, error: @error
|
183
|
+
|
184
|
+
class ExampleGroupWithoutHooks
|
185
|
+
constructor: (@assertions, @context, @blocks, @onFinish, @onError)-> @nextIndex = 0
|
186
|
+
|
187
|
+
run: ->
|
188
|
+
@wasSuccessful = true
|
189
|
+
setTimeout @nextTick, 0
|
190
|
+
|
191
|
+
nextTick: =>
|
192
|
+
(@onFinish(@wasSuccessful); return) unless @nextIndex < @blocks.length
|
193
|
+
block = @blocks[@nextIndex++]
|
194
|
+
new Example(block).run @assertions, @context, (error)=>
|
195
|
+
(@wasSuccessful = false; @onError error) if error
|
196
|
+
setTimeout @nextTick, 0
|
197
|
+
|
198
|
+
class Example
|
199
|
+
TICK = 10 # ms
|
200
|
+
constructor: (@exampleBlock)->
|
201
|
+
|
202
|
+
run: (@assertions, @context, @onFinish)->
|
203
|
+
@dsl = new ExampleDsl(@assertions.assert, @assertions.expect, @assertions.fail, \
|
204
|
+
@assertions.refute)
|
205
|
+
if @context
|
206
|
+
throw "runs and waitsFor are reserved attributes" if @context.runs or @context.waitsFor
|
207
|
+
@context.runs = @dsl.runs
|
208
|
+
@context.waitsFor = @dsl.waitsFor
|
209
|
+
@tryBlock @exampleBlock, ->
|
210
|
+
if @context
|
211
|
+
delete @context.runs
|
212
|
+
delete @context.waitsFor
|
213
|
+
(@onFinish(); return) unless (@steps = @dsl._asyncQueue_).length
|
214
|
+
@runNextAsyncStep()
|
215
|
+
|
216
|
+
tryBlock: (block, onSuccess)->
|
217
|
+
try
|
218
|
+
context = @context or @dsl
|
219
|
+
onSuccess.call this, block.call context, context
|
220
|
+
catch error
|
221
|
+
error = new Error(error) if typeof error is 'string'
|
222
|
+
error.message = "'#{error.message}' in '#{block.description}'" if block.description
|
223
|
+
@onFinish error
|
224
|
+
|
225
|
+
runNextAsyncStep: ->
|
226
|
+
(@onFinish(); return) unless @steps.length
|
227
|
+
step = @steps.shift()
|
228
|
+
if step instanceof Function
|
229
|
+
@tryBlock step, @runNextAsyncStep
|
230
|
+
else
|
231
|
+
@waitsFor step...
|
232
|
+
|
233
|
+
waitsFor: (@condition, timeout=1000, @description)->
|
234
|
+
@deadline = timeout + new Date().getTime()
|
235
|
+
@keepTryingCondition()
|
236
|
+
|
237
|
+
keepTryingCondition: =>
|
238
|
+
@tryBlock @condition, (result)->
|
239
|
+
(@runNextAsyncStep(); return) if result
|
240
|
+
(@onFinish {timeout: true, @description}; return) if new Date().getTime() > @deadline
|
241
|
+
setTimeout @keepTryingCondition, TICK
|
242
|
+
|
243
|
+
class ExampleDsl
|
244
|
+
constructor: (@assert, @expect, @fail, @refute)-> @_asyncQueue_ = []
|
245
|
+
|
246
|
+
runs: (step)=> @_asyncQueue_.push step
|
247
|
+
|
248
|
+
waitsFor: =>
|
249
|
+
for a in arguments
|
250
|
+
(condition = a; continue) if typeof a is "function"
|
251
|
+
(timeout = a; continue) if typeof a is "number"
|
252
|
+
(description = a; continue) if typeof a is "string"
|
253
|
+
@_asyncQueue_.push [condition, timeout, description]
|
254
|
+
|
255
|
+
class StepContext
|
256
|
+
constructor: (@assert, @expect, @fail)->
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Oojspec
|
2
|
+
class OojspecFilter
|
3
|
+
def self.filter(controller)
|
4
|
+
controller.template = 'oojspec/runner' if controller.params[:path].try :start_with?, 'oojspec'
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Engine < ::Rails::Engine
|
9
|
+
initializer 'sandbox_assets.oojspec' do |app|
|
10
|
+
unless app.config.sandbox_assets.template == 'oojspec/runner'
|
11
|
+
SandboxAssets::BaseController.prepend_before_filter OojspecFilter
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/oojspec.rb
ADDED
Binary file
|