red 4.1.0 → 4.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,525 @@
1
+ # Spec::DSL (module)
2
+ # should_fail
3
+ # should_be
4
+ # should_not_be
5
+ # should_be_empty
6
+ # should_not_be_empty
7
+ # should_be_true
8
+ # should_not_be_true
9
+ # should_be_false
10
+ # should_not_be_false
11
+ # should_be_nil
12
+ # should_not_be_nil
13
+ # should_have
14
+ # should_not_have
15
+ # should_have_exactly
16
+ # should_have_at_least
17
+ # should_have_at_most
18
+ # should_include
19
+ # should_not_include
20
+ # should_match
21
+ # should_not_match
22
+ #
23
+
24
+ # RedSpec is a BDD library designed for Red (url)
25
+ # RedSpec draws inspiration from both Ruby RSpec and Javascript JSSpec
26
+ # Using:
27
+ # RedSpec is intended for use with Red Herring, the framework-independant Red runner (url)
28
+
29
+
30
+ module DSL
31
+ # +DSL::Base+ represents the basic syntax that all Red objects should have to
32
+ # work properly with RedSpec. It also includes stub methods for methods that
33
+ # only object of a specific type should have. These methods will raise Specs::Failure when
34
+ # called because calling them on any object that does not include their
35
+ # fully implemented versions implies a specification that behaves
36
+ # differently than expected.
37
+ #
38
+ module Base
39
+ def should_equal(other)
40
+ raise ::Specs::Failure unless self == other
41
+ true
42
+ end
43
+
44
+ def should_be(other)
45
+ raise ::Specs::Failure unless self === other
46
+ true
47
+ end
48
+
49
+ def should_not_be(other)
50
+ raise ::Specs::Failure if self === other
51
+ true
52
+ end
53
+
54
+ def should_not_equal(other)
55
+ raise ::Specs::Failure if self == other
56
+ true
57
+ end
58
+
59
+ def should_not_be_nil
60
+ raise ::Specs::Failure if self == nil
61
+ end
62
+
63
+ # here for polymorphic purposes. all objects will need to respond
64
+ # to these but having these methods called implies you are
65
+ # are not getting a object you intended.
66
+ #
67
+ # For example, calling .should_be_true on an object that is
68
+ # not _true_ implies a specification failure by defintaiton
69
+ # and will raise Specs::Failure
70
+ def should_be_nil; raise ::Specs::Failure; end
71
+ def should_be_true; raise ::Specs::Failure; end
72
+ def should_be_false; raise ::Specs::Failure; end
73
+ def should_have(n); raise ::Specs::Failure; end
74
+ def items; raise ::Specs::Failure; end
75
+ end
76
+
77
+ #
78
+ module Nil
79
+ def should_be_nil
80
+ raise ::Specs::Failure unless self == nil
81
+ end
82
+
83
+ def should_not_be_nil
84
+ raise ::Specs::Failure if self == nil
85
+ end
86
+ end
87
+
88
+ module Boolean
89
+ def should_be_true
90
+ raise ::Specs::Failure unless `this.valueOf()`
91
+ end
92
+
93
+ def should_be_false
94
+ raise ::Specs::Failure if `this.valueOf()`
95
+ end
96
+ end
97
+
98
+ module Proc
99
+ def should_raise(specific_exception = nil)
100
+
101
+ end
102
+
103
+ def should_not_raise(specific_exception = nil)
104
+
105
+ end
106
+ end
107
+
108
+ module Array
109
+ def should_have(n)
110
+ raise ::Specs::Failure unless self.size == n
111
+ end
112
+
113
+ # just for looks to allow sugary syntax like
114
+ # @foo.bar.should_have(2).items
115
+ def items
116
+ true
117
+ end
118
+ end
119
+ end
120
+
121
+ String.include(DSL::Base)
122
+ Array.include(DSL::Base)
123
+ Hash.include(DSL::Base)
124
+ Numeric.include(DSL::Base)
125
+ Class.include(DSL::Base)
126
+ nil.extend(DSL::Base)
127
+ nil.extend(DSL::Nil)
128
+
129
+ `Red.donateMethodsToClass(#{DSL::Boolean}.prototype, Boolean.prototype)`
130
+
131
+ Array.include(DSL::Array)
132
+ Proc.include(DSL::Proc)
133
+
134
+
135
+ # Just stores the @@spec_list class variables. @@spec_list is an array of
136
+ # all the specs created with Spec.describe. This might go away with everthing nested inside
137
+ # of class Spec
138
+ class RedSpec
139
+ @@specs_list = []
140
+ def self.specs
141
+ @@specs_list
142
+ end
143
+
144
+ def self.escape_tags(string)
145
+ return string
146
+ end
147
+ end
148
+
149
+ class Spec
150
+ attr_accessor :name, :block, :examples, :runner
151
+
152
+ def self.describe(name, &block)
153
+ s = Spec.new(name, &block)
154
+ RedSpec.specs << s
155
+ block.call(s)
156
+ end
157
+
158
+ def initialize(name, &block)
159
+ @name = name.to_s
160
+ @block = block
161
+ @examples = []
162
+ end
163
+
164
+ # the meat of the verb calls on 'it' ('can', 'has', 'does', 'wants', etc).
165
+ # allows us to add new verbs and stay DRY.
166
+ def verb(display_verb, description, &block)
167
+ self.examples << ::Specs::Example.new((display_verb + " " + description), self, &block)
168
+ end
169
+
170
+ def can(description, &block)
171
+ self.verb("can", description, &block)
172
+ end
173
+
174
+ def returns(description, &block)
175
+ self.verb("returns", description, &block)
176
+ end
177
+
178
+ def has(description, &block)
179
+ self.verb("has", description, &block)
180
+ end
181
+
182
+ def does_not(description, &block)
183
+ self.verb("does not", description, &block)
184
+ end
185
+
186
+ def to_heading_html
187
+ "<li id=\"spec_#{self.object_id.to_s}_list\"><h3><a href=\"#spec_#{self.object_id.to_s}\"> #{RedSpec.escape_tags(self.name)}</a> [<a href=\"?rerun=#{self.name}\">rerun</a>]</h3></li>"
188
+ end
189
+
190
+ def examples_to_html
191
+ examples_as_text = []
192
+ self.examples.each do |example|
193
+ examples_as_text << example.to_html
194
+ end
195
+ examples_as_text.join('')
196
+ end
197
+
198
+ def to_html_with_examples
199
+ "<li id=\"spec_#{self.object_id.to_s}\">
200
+ <h3>#{RedSpec.escape_tags(self.name)} [<a href=\"?rerun=#{self.name}\">rerun</a>]</h3>
201
+ <ul id=\"spec_#{self.object_id.to_s}_examples\" class=\"examples\">
202
+ #{self.examples_to_html}
203
+ </ul>
204
+ </li>
205
+ "
206
+ end
207
+
208
+ def executor
209
+ ::Specs::Executor.new(self)
210
+ end
211
+ end
212
+
213
+ module Specs
214
+ # class +Failure+ is raise when an +Example+ fails to behave as intended.
215
+ # +Failure+ is rescued by the +Runner+ which notes the failure and
216
+ # continues to run subsequent Examples.
217
+ class Failure < Exception; end
218
+
219
+ # class +Error+ is raise when an +Example+ throws an error. Typically this
220
+ # is caused by errors of javascript syntax, missing variables, etc.
221
+ # Many Ruby syntax errors will be discovered by Red at compile time when
222
+ # ParseTree cannot parse the file.
223
+ # +Error+ is rescued by the +Runner+ which notes the error and
224
+ # continues to run subsequent Examples.
225
+ class Error < Exception; end
226
+
227
+ # each block within a spec is an example. typicall referenced with 'it' and one
228
+ # of the action verb methods ('should', 'can', 'has', etc)
229
+ #
230
+ # for example:
231
+ # Spec.describe Foo do |it|
232
+ #
233
+ # it.has 'pretty damn awesome bars' do
234
+ # # I am an Example object and will be called by the Runner
235
+ # end
236
+ #
237
+ # end
238
+ #
239
+ class Example
240
+ attr_accessor :block, :name, :result, :spec
241
+ def initialize(name, spec, &block)
242
+
243
+ # Examples without blocks will be listed as 'pending'
244
+ # and will not be executed by the Runner.
245
+ self.result = "pending" if block.nil?
246
+ @name = name
247
+ @spec = spec
248
+ @block = block
249
+ end
250
+
251
+ def to_html
252
+ "<li id=\"example_#{self.object_id.to_s}\">
253
+ <h4>#{RedSpec.escape_tags(self.name)}</h4>
254
+ </li>
255
+ "
256
+ end
257
+
258
+ def executor
259
+ ::Specs::Executor.new(self)
260
+ end
261
+
262
+ end
263
+
264
+ # responsible for gathering all specs from RedSpec.specs (or a subset if you're rerunning
265
+ # a particual spec) into one place and running them.
266
+ class Runner
267
+ attr_accessor :specs, :specs_map, :total_examples, :logger, :ordered_executor, :total_failures, :total_errors, :total_pending
268
+ def initialize(arg_specs, logger)
269
+ logger.runner = self
270
+ @logger = logger
271
+
272
+ @specs = []
273
+ @specs_map = {}
274
+
275
+ self.total_examples = 0
276
+ self.total_pending = 0
277
+ self.total_failures = 0
278
+ self.add_all_specs(arg_specs)
279
+ end
280
+
281
+ def add_all_specs(specs)
282
+ specs.each {|spec| self.add_spec(spec)}
283
+ end
284
+
285
+ def add_spec(spec)
286
+ spec.runner = self
287
+ self.specs << spec
288
+ # self.specs_map[spec.object_id] = spec
289
+ self.total_examples += spec.examples.size
290
+ end
291
+
292
+ def run
293
+ self.logger.on_runner_start
294
+ self.ordered_executor = Specs::OrderedExecutor.new
295
+ self.specs.each do |spec|
296
+ spec.examples.each do |example|
297
+ self.ordered_executor.add_executor(example.executor)
298
+ end
299
+ end
300
+
301
+ self.ordered_executor.run
302
+ self.logger.on_runner_end
303
+ end
304
+
305
+ # def get_spec_by_id ; end
306
+ # def get_spec_by_context ; end
307
+ # def has_exception ; end
308
+ # def total_failures ; end
309
+ # def total_errors ; end
310
+ # def rerun ; end
311
+
312
+ end
313
+
314
+ # executes the code of the examples in each spec
315
+ # and stores their state (success/failure) and any
316
+ # failure messages, normalized for browser differences
317
+ class Executor
318
+ attr_accessor :example, :type, :containing_ordered_executor, :on_start
319
+
320
+ def initialize(example)
321
+ self.example = example
322
+ end
323
+
324
+ def run
325
+ ::Specs::Logger.on_example_start(self.example)
326
+
327
+ begin
328
+ if self.example.result # result was set to pending on Example#initialize
329
+ self.type = 'pending'
330
+ self.example.spec.runner.total_pending += 1
331
+ else
332
+ self.example.block.call
333
+ end
334
+
335
+ self.type = 'success'
336
+ self.example.result = 'success' unless self.example.result
337
+
338
+ rescue ::Specs::Failure
339
+ self.type = 'failure'
340
+ self.example.result = 'failure'
341
+ self.example.spec.runner.total_failures += 1
342
+ rescue Exception
343
+ self.example.rescue = 'exception'
344
+ self.example.spec.runner.total_errors += 1
345
+ end
346
+
347
+ ::Specs::Logger.on_example_end(self.example)
348
+
349
+ self.containing_ordered_executor.next
350
+ end
351
+ end
352
+
353
+
354
+ class OrderedExecutor
355
+ attr_accessor :queue, :at
356
+
357
+ def initialize
358
+ @queue = []
359
+ self.at = 0
360
+ end
361
+
362
+ def add_executor(executor)
363
+ # some other stuff with callbacks? no idea
364
+ executor.containing_ordered_executor = self
365
+ self.queue << executor
366
+ end
367
+
368
+ # Runs the next Executor in the queue.
369
+ def next
370
+ self.at += 1
371
+ self.queue[self.at].run unless self.at >= self.queue.size
372
+ end
373
+
374
+ def run
375
+ if self.queue.size > 0
376
+ self.queue[0].run
377
+ end
378
+ end
379
+ end
380
+
381
+ # Logger writes the pretty to the screen
382
+ class Logger
383
+ attr_accessor :runner, :started_at, :ended_at
384
+
385
+ # on_runner_start is called just before the specs are run and writes the general logging
386
+ # structure to the page for later manipulation
387
+ def on_runner_start
388
+ title = `document.title`
389
+ self.started_at = Time.now
390
+ # self.runnetotal_failures = 0
391
+
392
+ # if container already exists it implies we are rerunning the
393
+ # specs and the contents of the div should be cleared.
394
+ # Otherwise we added a containing div to the page
395
+ # to hold our Logger printout.
396
+ container = `document.getElementById('redspec_container')`
397
+ if container
398
+ `container.innerHTML = ""`
399
+ else
400
+ `container = document.createElement("DIV")`
401
+ `container.id = "redspec_container"`
402
+ `document.body.appendChild(container)`
403
+ end
404
+
405
+ # The dashboard contains at-a-glace information about the running/competed specs
406
+ # allowing a tester to see see a summary of
407
+ `dashboard = document.createElement("DIV")`
408
+ `dashboard.id = "dashboard"`
409
+ `dashboard.innerHTML = [
410
+ '<h1>RedSpec</h1>',
411
+ '<ul>',
412
+ // JSSpec.options.rerun ? '<li>[<a href="?" title="rerun all specs">X</a>] ' + JSSpec.util.escapeTags(decodeURIComponent(JSSpec.options.rerun)) + '</li>' : '',
413
+ ' <li><span id="total_examples">' + #{self.runner.total_examples} + '</span> examples</li>',
414
+ ' <li><span id="total_failures">0</span> failures</li>',
415
+ ' <li><span id="total_errors">0</span> errors</li>',
416
+ ' <li><span id="total_pending">0</span> pending</li>',
417
+ ' <li><span id="progress">0</span>% done</li>',
418
+ ' <li><span id="total_elapsed">0</span> secs</li>',
419
+ '</ul>',
420
+ '<p><a href="">RedSpec documentation</a></p>',
421
+ ].join("");`
422
+
423
+ `container.appendChild(dashboard);`
424
+
425
+ # convert all of the specs for this runner into native js strings for writing
426
+ all_runner_specs = []
427
+ self.runner.specs.each do |spec|
428
+ all_runner_specs << spec.to_heading_html
429
+ end
430
+ `all_runner_specs_as_list_items = #{all_runner_specs.join("")}.__value__`
431
+
432
+ # List the Specs by name to act as a table of contents
433
+ `list = document.createElement("DIV")`
434
+ `list.id = "list"`
435
+ `list.innerHTML = [
436
+ '<h2>Specs</h2>',
437
+ '<ul class="specs">',
438
+ all_runner_specs_as_list_items,
439
+ '</ul>'
440
+ ].join("")`
441
+ `container.appendChild(list)`
442
+
443
+
444
+ # List all the examples, nested within their Spec name
445
+ # so we can later manipulate their element to display
446
+ # results of running a particular example.
447
+ `log = document.createElement("DIV")`
448
+ `log.id = "log"`
449
+
450
+ all_runner_specs_with_examples = []
451
+ self.runner.specs.each do |spec|
452
+ all_runner_specs_with_examples << spec.to_html_with_examples
453
+ end
454
+ `all_runner_specs_as_list_items_with_examples = #{all_runner_specs_with_examples.join("")}.__value__`
455
+
456
+ `log.innerHTML = [
457
+ '<h2>Log</h2>',
458
+ '<ul class="specs">',
459
+ all_runner_specs_as_list_items_with_examples,
460
+ '</ul>'
461
+ ].join("")`
462
+
463
+ `container.appendChild(log)`
464
+
465
+ # add event click handler to each spec for toggling
466
+ self.runner.specs.each do |spec|
467
+ # `spec_div = document.getElementById('spec_' + #{spec.object_id})`
468
+ # `title = spec_div.getElementsByTagName("H3")[0]`
469
+ # `title.onclick = function(e){
470
+ # var target = document.getElementById(this.parentNode.id + "_examples")
471
+ # target.style.display = target.style.display == 'none' ? 'block' : 'none'
472
+ # return true
473
+ # }`
474
+ end
475
+ end
476
+
477
+ # called automatically when a runner ends, updating the dashboard with result information for
478
+ # the entire spec suite.
479
+ def on_runner_end
480
+ `document.getElementById("total_elapsed").innerHTML = (#{Time.now - self.started_at })`
481
+ `document.getElementById("total_failures").innerHTML = #{self.runner.total_errors}`
482
+ `document.getElementById("total_failures").innerHTML = #{self.runner.total_failures}`
483
+ `document.getElementById("total_pending").innerHTML = #{self.runner.total_pending}`
484
+ end
485
+
486
+ def self.on_spec_start(spec)
487
+ `spec_list = document.getElementById("spec_" + spec.id + "_list")`
488
+ `spec_log = document.getElementById("spec_" + spec.id)`
489
+
490
+ `spec_list.className = "ongoing"`
491
+ `spec_log.className = "ongoing"`
492
+ end
493
+
494
+ def self.on_spec_end(spec)
495
+
496
+ end
497
+
498
+ # called before an example runs
499
+ def self.on_example_start(example)
500
+ `li = document.getElementById("example_" + #{example.object_id.to_s})`
501
+ `li.className = "ongoing"`
502
+ end
503
+
504
+ # called after an example runs, manipulating the examples representation on the page
505
+ # to reflect the result of the execution.
506
+ def self.on_example_end(example)
507
+ `li = document.getElementById("example_" + #{example.object_id.to_s})`
508
+ `li.className = #{example.result}.__value__`
509
+ end
510
+
511
+ end
512
+ end
513
+
514
+
515
+ main = lambda {
516
+ if RedSpec.specs.size > 0
517
+ r = Specs::Runner.new(RedSpec.specs, Specs::Logger.new)
518
+ r.run
519
+ end
520
+ }
521
+
522
+ # Wait for the window to load and then determing run the specs
523
+ # `window.onload = #{main}.__block__`
524
+
525
+ `document.addEventListener('DOMContentLoaded', function(){document.__loaded__=true;#{main.call};}.m$(this), false)`