oojspec 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|