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 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,5 @@
1
+ /* =require buster/buster-test */
2
+ /* override background image for supporting IE7 */
3
+ .buster-test h1 .buster-logo {
4
+ background-image: url(<%= asset_path "buster/logo.png" %>);
5
+ }
@@ -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
@@ -0,0 +1,3 @@
1
+ module Oojspec
2
+ VERSION = "0.0.1"
3
+ end
data/lib/oojspec.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "oojspec/engine"
2
+ require "rails-sandbox-assets"
3
+
4
+ module Oojspec
5
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :oojspec do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,3 @@
1
+ # =require ./buster-core
2
+ # =require ./buster-event-emitter
3
+ # =require_tree .