red 4.1.0 → 4.1.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.
@@ -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)`