raus22-jspec 2.0.4

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.
Files changed (45) hide show
  1. data/History.rdoc +323 -0
  2. data/Manifest +44 -0
  3. data/README.rdoc +495 -0
  4. data/Rakefile +72 -0
  5. data/bin/jspec +137 -0
  6. data/jspec.gemspec +35 -0
  7. data/lib/images/bg.png +0 -0
  8. data/lib/images/hr.png +0 -0
  9. data/lib/images/loading.gif +0 -0
  10. data/lib/images/sprites.bg.png +0 -0
  11. data/lib/images/sprites.png +0 -0
  12. data/lib/images/vr.png +0 -0
  13. data/lib/jspec.css +145 -0
  14. data/lib/jspec.jquery.js +64 -0
  15. data/lib/jspec.js +1468 -0
  16. data/server/browsers.rb +16 -0
  17. data/server/server.rb +103 -0
  18. data/spec/async +1 -0
  19. data/spec/jquery-1.3.1.js +4241 -0
  20. data/spec/server.html +23 -0
  21. data/spec/spec.grammar-less.js +34 -0
  22. data/spec/spec.grammar.js +179 -0
  23. data/spec/spec.html +27 -0
  24. data/spec/spec.jquery.js +166 -0
  25. data/spec/spec.js +119 -0
  26. data/spec/spec.matchers.js +382 -0
  27. data/spec/spec.rhino.js +12 -0
  28. data/spec/spec.shared-behaviors.js +51 -0
  29. data/spec/spec.utils.js +138 -0
  30. data/templates/default/History.rdoc +4 -0
  31. data/templates/default/README.rdoc +29 -0
  32. data/templates/default/lib/yourlib.core.js +2 -0
  33. data/templates/default/spec/spec.core.js +8 -0
  34. data/templates/default/spec/spec.html +20 -0
  35. data/templates/rhino/History.rdoc +4 -0
  36. data/templates/rhino/README.rdoc +29 -0
  37. data/templates/rhino/lib/yourlib.core.js +2 -0
  38. data/templates/rhino/spec/spec.core.js +8 -0
  39. data/templates/rhino/spec/spec.js +7 -0
  40. data/templates/server/History.rdoc +4 -0
  41. data/templates/server/README.rdoc +29 -0
  42. data/templates/server/lib/yourlib.core.js +2 -0
  43. data/templates/server/spec/spec.core.js +8 -0
  44. data/templates/server/spec/spec.html +15 -0
  45. metadata +120 -0
@@ -0,0 +1,1468 @@
1
+
2
+ // JSpec - Core - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
3
+
4
+ (function(){
5
+
6
+ JSpec = {
7
+
8
+ version : '2.0.3',
9
+ suites : [],
10
+ allSuites : [],
11
+ matchers : {},
12
+ stats : { specs : 0, assertions : 0, failures : 0, passes : 0, specsFinished : 0, suitesFinished : 0 },
13
+ options : { profile : false },
14
+
15
+ /**
16
+ * Default context in which bodies are evaluated.
17
+ *
18
+ * Replace context simply by setting JSpec.context
19
+ * to your own like below:
20
+ *
21
+ * JSpec.context = { foo : 'bar' }
22
+ *
23
+ * Contexts can be changed within any body, this can be useful
24
+ * in order to provide specific helper methods to specific suites.
25
+ *
26
+ * To reset (usually in after hook) simply set to null like below:
27
+ *
28
+ * JSpec.context = null
29
+ *
30
+ */
31
+
32
+ defaultContext : {
33
+
34
+ /**
35
+ * Return an object used for proxy assertions.
36
+ * This object is used to indicate that an object
37
+ * should be an instance of _object_, not the constructor
38
+ * itself.
39
+ *
40
+ * @param {function} constructor
41
+ * @return {hash}
42
+ * @api public
43
+ */
44
+
45
+ an_instance_of : function(constructor) {
46
+ return { an_instance_of : constructor }
47
+ },
48
+
49
+ /**
50
+ * Sets the current spec's wait duration to _n_.
51
+ *
52
+ * wait(3000)
53
+ * wait(1, 'second')
54
+ * wait(3, 'seconds')
55
+ *
56
+ * @param {number} n
57
+ * @param {string} unit
58
+ * @api public
59
+ */
60
+
61
+ wait : function(n, unit) {
62
+ JSpec.currentSpec.wait = {
63
+ 'second' : n * 1000,
64
+ 'seconds' : n * 1000
65
+ }[unit] || n
66
+ }
67
+ },
68
+
69
+ // --- Objects
70
+
71
+ formatters : {
72
+
73
+ /**
74
+ * Default formatter, outputting to the DOM.
75
+ *
76
+ * Options:
77
+ * - reportToId id of element to output reports to, defaults to 'jspec'
78
+ * - failuresOnly displays only suites with failing specs
79
+ *
80
+ * @api public
81
+ */
82
+
83
+ DOM : function(results, options) {
84
+ var id = option('reportToId') || 'jspec'
85
+ var report = document.getElementById(id)
86
+ var failuresOnly = option('failuresOnly')
87
+ var classes = results.stats.failures ? 'has-failures' : ''
88
+ if (!report) throw 'JSpec requires the element #' + id + ' to output its reports'
89
+
90
+ var markup =
91
+ '<div id="jspec-report" class="' + classes + '"><div class="heading"> \
92
+ <span class="passes">Passes: <em>' + results.stats.passes + '</em></span> \
93
+ <span class="failures">Failures: <em>' + results.stats.failures + '</em></span> \
94
+ </div><table class="suites">'
95
+
96
+ bodyContents = function(body) {
97
+ return JSpec.
98
+ escape(JSpec.contentsOf(body)).
99
+ replace(/^ */gm, function(a){ return (new Array(Math.round(a.length / 3))).join(' ') }).
100
+ replace("\n", '<br/>')
101
+ }
102
+
103
+ renderSuite = function(suite) {
104
+ var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
105
+ if (displaySuite && suite.hasSpecs()) {
106
+ markup += '<tr class="description"><td colspan="2">' + suite.description + '</td></tr>'
107
+ each(suite.specs, function(i, spec){
108
+ markup += '<tr class="' + (i % 2 ? 'odd' : 'even') + '">'
109
+ if (spec.requiresImplementation())
110
+ markup += '<td class="requires-implementation" colspan="2">' + spec.description + '</td>'
111
+ else if (spec.passed() && !failuresOnly)
112
+ markup += '<td class="pass">' + spec.description+ '</td><td>' + spec.assertionsGraph() + '</td>'
113
+ else if(!spec.passed())
114
+ markup += '<td class="fail">' + spec.description + ' <em>' + spec.failure().message + '</em>' + '</td><td>' + spec.assertionsGraph() + '</td>'
115
+ markup += '<tr class="body"><td colspan="2"><pre>' + bodyContents(spec.body) + '</pre></td></tr>'
116
+ })
117
+ markup += '</tr>'
118
+ }
119
+ }
120
+
121
+ renderSuites = function(suites) {
122
+ each(suites, function(suite){
123
+ renderSuite(suite)
124
+ if (suite.hasSuites()) renderSuites(suite.suites)
125
+ })
126
+ }
127
+
128
+ renderSuites(results.suites)
129
+ markup += '</table></div>'
130
+ report.innerHTML = markup
131
+ },
132
+
133
+ /**
134
+ * Terminal formatter.
135
+ *
136
+ * @api public
137
+ */
138
+
139
+ Terminal : function(results, options) {
140
+ failuresOnly = option('failuresOnly')
141
+ print(color("\n Passes: ", 'bold') + color(results.stats.passes, 'green') +
142
+ color(" Failures: ", 'bold') + color(results.stats.failures, 'red') + "\n")
143
+
144
+ indent = function(string) {
145
+ return string.replace(/^(.)/gm, ' $1')
146
+ }
147
+
148
+ renderSuite = function(suite) {
149
+ displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
150
+ if (displaySuite && suite.hasSpecs()) {
151
+ print(color(' ' + suite.description, 'bold'))
152
+ each(suite.specs, function(spec){
153
+ var assertionsGraph = inject(spec.assertions, '', function(graph, assertion){
154
+ return graph + color('.', assertion.passed ? 'green' : 'red')
155
+ })
156
+ if (spec.requiresImplementation())
157
+ print(color(' ' + spec.description, 'blue') + assertionsGraph)
158
+ else if (spec.passed() && !failuresOnly)
159
+ print(color(' ' + spec.description, 'green') + assertionsGraph)
160
+ else if (!spec.passed())
161
+ print(color(' ' + spec.description, 'red') + assertionsGraph +
162
+ "\n" + indent(spec.failure().message) + "\n")
163
+ })
164
+ print("")
165
+ }
166
+ }
167
+
168
+ renderSuites = function(suites) {
169
+ each(suites, function(suite){
170
+ renderSuite(suite)
171
+ if (suite.hasSuites()) renderSuites(suite.suites)
172
+ })
173
+ }
174
+
175
+ renderSuites(results.suites)
176
+ },
177
+
178
+ /**
179
+ * Console formatter, tested with Firebug and Safari 4.
180
+ *
181
+ * @api public
182
+ */
183
+
184
+ Console : function(results, options) {
185
+ console.log('')
186
+ console.log('Passes: ' + results.stats.passes + ' Failures: ' + results.stats.failures)
187
+
188
+ renderSuite = function(suite) {
189
+ if (suite.ran) {
190
+ console.group(suite.description)
191
+ each(suite.specs, function(spec){
192
+ var assertionCount = spec.assertions.length + ':'
193
+ if (spec.requiresImplementation())
194
+ console.warn(spec.description)
195
+ else if (spec.passed())
196
+ console.log(assertionCount + ' ' + spec.description)
197
+ else
198
+ console.error(assertionCount + ' ' + spec.description + ', ' + spec.failure().message)
199
+ })
200
+ console.groupEnd()
201
+ }
202
+ }
203
+
204
+ renderSuites = function(suites) {
205
+ each(suites, function(suite){
206
+ renderSuite(suite)
207
+ if (suite.hasSuites()) renderSuites(suite.suites)
208
+ })
209
+ }
210
+
211
+ renderSuites(results.suites)
212
+ }
213
+ },
214
+
215
+ Assertion : function(matcher, actual, expected, negate) {
216
+ extend(this, {
217
+ message : '',
218
+ passed : false,
219
+ actual : actual,
220
+ negate : negate,
221
+ matcher : matcher,
222
+ expected : expected,
223
+
224
+ // Report assertion results
225
+
226
+ report : function() {
227
+ this.passed ? JSpec.stats.passes++ : JSpec.stats.failures++
228
+ return this
229
+ },
230
+
231
+ // Run the assertion
232
+
233
+ run : function() {
234
+ // TODO: remove unshifting
235
+ expected.unshift(actual)
236
+ this.result = matcher.match.apply(this, expected)
237
+ this.passed = negate ? !this.result : this.result
238
+ if (!this.passed) this.message = matcher.message.call(this, actual, expected, negate, matcher.name)
239
+ return this
240
+ }
241
+ })
242
+ },
243
+
244
+ ProxyAssertion : function(object, method, times) {
245
+ var self = this
246
+ var old = object[method]
247
+
248
+ // Proxy
249
+
250
+ object[method] = function(){
251
+ args = argumentsToArray(arguments)
252
+ result = old.apply(object, args)
253
+ self.calls.push({ args : args, result : result })
254
+ return result
255
+ }
256
+
257
+ // Times
258
+
259
+ this.times = {
260
+ 'once' : 1,
261
+ 'twice' : 2
262
+ }[times] || times || 1
263
+
264
+ // TODO: negation
265
+
266
+ extend(this, {
267
+ calls : [],
268
+ message : '',
269
+ defer : true,
270
+ passed : false,
271
+ object : object,
272
+ method : method,
273
+
274
+ // Proxy return value
275
+
276
+ and_return : function(result) {
277
+ this.expectedResult = result
278
+ return this
279
+ },
280
+
281
+ // Proxy arguments passed
282
+
283
+ with_args : function() {
284
+ this.expectedArgs = argumentsToArray(arguments)
285
+ return this
286
+ },
287
+
288
+ // Check if any calls have failing results
289
+
290
+ anyResultsFail : function() {
291
+ return any(this.calls, function(call){
292
+ return self.expectedResult.an_instance_of ?
293
+ call.result.constructor != self.expectedResult.an_instance_of:
294
+ hash(self.expectedResult) != hash(call.result)
295
+ })
296
+ },
297
+
298
+ // Return the failing result
299
+
300
+ failingResult : function() {
301
+ return this.anyResultsFail().result
302
+ },
303
+
304
+ // Check if any arguments fail
305
+
306
+ anyArgsFail : function() {
307
+ return any(this.calls, function(call){
308
+ return any(self.expectedArgs, function(i, arg){
309
+ return arg.an_instance_of ?
310
+ call.args[i].constructor != arg.an_instance_of:
311
+ hash(arg) != hash(call.args[i])
312
+
313
+ })
314
+ })
315
+ },
316
+
317
+ // Return the failing args
318
+
319
+ failingArgs : function() {
320
+ return this.anyArgsFail().args
321
+ },
322
+
323
+ // Report assertion results
324
+
325
+ report : function() {
326
+ this.passed ? JSpec.stats.passes++ : JSpec.stats.failures++
327
+ return this
328
+ },
329
+
330
+ // Run the assertion
331
+
332
+ run : function() {
333
+ var methodString = 'expected ' + object.toString() + '.' + method + '()'
334
+
335
+ function times(n) {
336
+ return n > 2 ? n + ' times' : { 1 : 'once', 2 : 'twice' }[n]
337
+ }
338
+
339
+ if (this.calls.length < this.times)
340
+ this.message = methodString + ' to be called ' + times(this.times) +
341
+ ', but ' + (this.calls.length == 0 ? ' was not called' : ' was called ' + times(this.calls.length))
342
+
343
+ if (this.expectedResult && this.anyResultsFail())
344
+ this.message = methodString + ' to return ' + puts(this.expectedResult) +
345
+ ' but got ' + puts(this.failingResult())
346
+
347
+ if (this.expectedArgs && this.anyArgsFail())
348
+ this.message = methodString + ' to be called with ' + puts.apply(this, this.expectedArgs) +
349
+ ' but was called with ' + puts.apply(this, this.failingArgs())
350
+
351
+ if (!this.message.length)
352
+ this.passed = true
353
+
354
+ return this
355
+ }
356
+ })
357
+ },
358
+
359
+ /**
360
+ * Specification Suite block object.
361
+ *
362
+ * @param {string} description
363
+ * @param {function} body
364
+ * @api private
365
+ */
366
+
367
+ Suite : function(description, body) {
368
+ var self = this
369
+ extend(this, {
370
+ body: body,
371
+ description: description,
372
+ suites: [],
373
+ specs: [],
374
+ ran: false,
375
+ hooks: { 'before' : [], 'after' : [], 'before_each' : [], 'after_each' : [] },
376
+
377
+ // Add a spec to the suite
378
+
379
+ addSpec : function(description, body) {
380
+ var spec = new JSpec.Spec(description, body)
381
+ this.specs.push(spec)
382
+ JSpec.stats.specs++ // TODO: abstract
383
+ spec.suite = this
384
+ },
385
+
386
+ // Add a hook to the suite
387
+
388
+ addHook : function(hook, body) {
389
+ this.hooks[hook].push(body)
390
+ },
391
+
392
+ // Add a nested suite
393
+
394
+ addSuite : function(description, body) {
395
+ var suite = new JSpec.Suite(description, body)
396
+ JSpec.allSuites.push(suite)
397
+ suite.name = suite.description
398
+ suite.description = this.description + ' ' + suite.description
399
+ this.suites.push(suite)
400
+ suite.suite = this
401
+ },
402
+
403
+ // Invoke a hook in context to this suite
404
+
405
+ hook : function(hook) {
406
+ if (this.suite) this.suite.hook(hook)
407
+ each(this.hooks[hook], function(body) {
408
+ JSpec.evalBody(body, "Error in hook '" + hook + "', suite '" + self.description + "': ")
409
+ })
410
+ },
411
+
412
+ // Check if nested suites are present
413
+
414
+ hasSuites : function() {
415
+ return this.suites.length
416
+ },
417
+
418
+ // Check if this suite has specs
419
+
420
+ hasSpecs : function() {
421
+ return this.specs.length
422
+ },
423
+
424
+ // Check if the entire suite passed
425
+
426
+ passed : function() {
427
+ return !any(this.specs, function(spec){
428
+ return !spec.passed()
429
+ })
430
+ }
431
+ })
432
+ },
433
+
434
+ /**
435
+ * Specification block object.
436
+ *
437
+ * @param {string} description
438
+ * @param {function} body
439
+ * @api private
440
+ */
441
+
442
+ Spec : function(description, body) {
443
+ extend(this, {
444
+ body : body,
445
+ description : description,
446
+ assertions : [],
447
+
448
+ // Run deferred assertions
449
+
450
+ runDeferredAssertions : function() {
451
+ each(this.assertions, function(assertion){
452
+ if (assertion.defer) assertion.run().report()
453
+ })
454
+ },
455
+
456
+ // Find first failing assertion
457
+
458
+ failure : function() {
459
+ return find(this.assertions, function(assertion){
460
+ return !assertion.passed
461
+ })
462
+ },
463
+
464
+ // Find all failing assertions
465
+
466
+ failures : function() {
467
+ return select(this.assertions, function(assertion){
468
+ return !assertion.passed
469
+ })
470
+ },
471
+
472
+ // Weither or not the spec passed
473
+
474
+ passed : function() {
475
+ return !this.failure()
476
+ },
477
+
478
+ // Weither or not the spec requires implementation (no assertions)
479
+
480
+ requiresImplementation : function() {
481
+ return this.assertions.length == 0
482
+ },
483
+
484
+ // Sprite based assertions graph
485
+
486
+ assertionsGraph : function() {
487
+ return map(this.assertions, function(assertion){
488
+ return '<span class="assertion ' + (assertion.passed ? 'passed' : 'failed') + '"></span>'
489
+ }).join('')
490
+ }
491
+ })
492
+ },
493
+
494
+ // --- DSLs
495
+
496
+ DSLs : {
497
+ snake : {
498
+ expect : function(actual){
499
+ return JSpec.expect(actual)
500
+ },
501
+
502
+ describe : function(description, body) {
503
+ return JSpec.currentSuite.addSuite(description, body)
504
+ },
505
+
506
+ it : function(description, body) {
507
+ return JSpec.currentSuite.addSpec(description, body)
508
+ },
509
+
510
+ before : function(body) {
511
+ return JSpec.currentSuite.addHook('before', body)
512
+ },
513
+
514
+ after : function(body) {
515
+ return JSpec.currentSuite.addHook('after', body)
516
+ },
517
+
518
+ before_each : function(body) {
519
+ return JSpec.currentSuite.addHook('before_each', body)
520
+ },
521
+
522
+ after_each : function(body) {
523
+ return JSpec.currentSuite.addHook('after_each', body)
524
+ },
525
+
526
+ should_behave_like : function(description) {
527
+ return JSpec.shareBehaviorsOf(description)
528
+ }
529
+ }
530
+ },
531
+
532
+ // --- Methods
533
+
534
+ /**
535
+ * Find a suite by its description or name.
536
+ *
537
+ * @param {string} description
538
+ * @return {Suite}
539
+ * @api private
540
+ */
541
+
542
+ findSuite : function(description) {
543
+ return find(this.allSuites, function(suite){
544
+ return suite.name == description || suite.description == description
545
+ })
546
+ },
547
+
548
+ /**
549
+ * Share behaviors (specs) of the given suite with
550
+ * the current suite.
551
+ *
552
+ * @param {string} description
553
+ * @api public
554
+ */
555
+
556
+ shareBehaviorsOf : function(description) {
557
+ if (suite = this.findSuite(description)) this.copySpecs(suite, this.currentSuite)
558
+ else throw 'failed to share behaviors. ' + puts(description) + ' is not a valid Suite name'
559
+ },
560
+
561
+ /**
562
+ * Copy specs from one suite to another.
563
+ *
564
+ * @param {Suite} fromSuite
565
+ * @param {Suite} toSuite
566
+ * @api public
567
+ */
568
+
569
+ copySpecs : function(fromSuite, toSuite) {
570
+ each(fromSuite.specs, function(spec){
571
+ toSuite.specs.push(spec)
572
+ })
573
+ },
574
+
575
+ /**
576
+ * Convert arguments to an array.
577
+ *
578
+ * @param {object} arguments
579
+ * @param {int} offset
580
+ * @return {array}
581
+ * @api public
582
+ */
583
+
584
+ argumentsToArray : function(arguments, offset) {
585
+ var args = []
586
+ for (i = 0; i < arguments.length; i++) args.push(arguments[i])
587
+ return args.slice(offset || 0)
588
+ },
589
+
590
+ /**
591
+ * Return ANSI-escaped colored string.
592
+ *
593
+ * @param {string} string
594
+ * @param {string} color
595
+ * @return {string}
596
+ * @api public
597
+ */
598
+
599
+ color : function(string, color) {
600
+ return "\u001B[" + {
601
+ bold : 1,
602
+ black : 30,
603
+ red : 31,
604
+ green : 32,
605
+ yellow : 33,
606
+ blue : 34,
607
+ magenta : 35,
608
+ cyan : 36,
609
+ white : 37
610
+ }[color] + 'm' + string + "\u001B[0m"
611
+ },
612
+
613
+ /**
614
+ * Default matcher message callback.
615
+ *
616
+ * @api private
617
+ */
618
+
619
+ defaultMatcherMessage : function(actual, expected, negate, name) {
620
+ return 'expected ' + puts(actual) + ' to ' +
621
+ (negate ? 'not ' : '') +
622
+ name.replace(/_/g, ' ') +
623
+ ' ' + puts.apply(this, expected.slice(1))
624
+ },
625
+
626
+ /**
627
+ * Normalize a matcher message.
628
+ *
629
+ * When no messge callback is present the defaultMatcherMessage
630
+ * will be assigned, will suffice for most matchers.
631
+ *
632
+ * @param {hash} matcher
633
+ * @return {hash}
634
+ * @api public
635
+ */
636
+
637
+ normalizeMatcherMessage : function(matcher) {
638
+ if (typeof matcher.message != 'function')
639
+ matcher.message = this.defaultMatcherMessage
640
+ return matcher
641
+ },
642
+
643
+ /**
644
+ * Normalize a matcher body
645
+ *
646
+ * This process allows the following conversions until
647
+ * the matcher is in its final normalized hash state.
648
+ *
649
+ * - '==' becomes 'actual == expected'
650
+ * - 'actual == expected' becomes 'return actual == expected'
651
+ * - function(actual, expected) { return actual == expected } becomes
652
+ * { match : function(actual, expected) { return actual == expected }}
653
+ *
654
+ * @param {mixed} body
655
+ * @return {hash}
656
+ * @api public
657
+ */
658
+
659
+ normalizeMatcherBody : function(body) {
660
+ switch (body.constructor) {
661
+ case String:
662
+ if (captures = body.match(/^alias (\w+)/)) return JSpec.matchers[last(captures)]
663
+ if (body.length < 4) body = 'actual ' + body + ' expected'
664
+ return { match : function(actual, expected) { return eval(body) }}
665
+
666
+ case Function:
667
+ return { match : body }
668
+
669
+ default:
670
+ return body
671
+ }
672
+ },
673
+
674
+ /**
675
+ * Get option value. This method first checks if
676
+ * the option key has been set via the query string,
677
+ * otherwise returning the options hash value.
678
+ *
679
+ * @param {string} key
680
+ * @return {mixed}
681
+ * @api public
682
+ */
683
+
684
+ option : function(key) {
685
+ return (value = query(key)) !== null ? value :
686
+ JSpec.options[key] || null
687
+ },
688
+
689
+ /**
690
+ * Generates a hash of the object passed.
691
+ *
692
+ * @param {object} object
693
+ * @return {string}
694
+ * @api private
695
+ */
696
+
697
+ hash : function(object) {
698
+ if (object == undefined) return 'undefined'
699
+ serialize = function(prefix) {
700
+ return inject(object, prefix + ':', function(buffer, key, value){
701
+ return buffer += hash(value)
702
+ })
703
+ }
704
+ switch (object.constructor) {
705
+ case Array : return serialize('a')
706
+ case Object: return serialize('o')
707
+ case RegExp: return 'r:' + object.toString()
708
+ case Number: return 'n:' + object.toString()
709
+ case String: return 's:' + object.toString()
710
+ default: return object.toString()
711
+ }
712
+ },
713
+
714
+ /**
715
+ * Return last element of an array.
716
+ *
717
+ * @param {array} array
718
+ * @return {object}
719
+ * @api public
720
+ */
721
+
722
+ last : function(array) {
723
+ return array[array.length - 1]
724
+ },
725
+
726
+ /**
727
+ * Convert object(s) to a print-friend string.
728
+ *
729
+ * @param {object, ...} object
730
+ * @return {string}
731
+ * @api public
732
+ */
733
+
734
+ puts : function(object) {
735
+ if (arguments.length > 1) {
736
+ return map(argumentsToArray(arguments), function(arg){
737
+ return puts(arg)
738
+ }).join(', ')
739
+ }
740
+ if (object === undefined) return ''
741
+ if (object === null) return 'null'
742
+ if (object === true) return 'true'
743
+ if (object === false) return 'false'
744
+ if (object.an_instance_of) return 'an instance of ' + object.an_instance_of.name
745
+ if (object.jquery && object.selector.length > 0) return 'selector ' + puts(object.selector) + ''
746
+ if (object.jquery) return escape(object.html())
747
+ if (object.nodeName) return escape(object.outerHTML)
748
+ switch (object.constructor) {
749
+ case String: return "'" + escape(object) + "'"
750
+ case Number: return object
751
+ case Function: return object.name || object
752
+ case Array :
753
+ return inject(object, '[', function(b, v){
754
+ return b + ', ' + puts(v)
755
+ }).replace('[,', '[') + ' ]'
756
+ case Object:
757
+ return inject(object, '{', function(b, k, v) {
758
+ return b + ', ' + puts(k) + ' : ' + puts(v)
759
+ }).replace('{,', '{') + ' }'
760
+ default:
761
+ return escape(object.toString())
762
+ }
763
+ },
764
+
765
+ /**
766
+ * Escape HTML.
767
+ *
768
+ * @param {string} html
769
+ * @return {string}
770
+ * @api public
771
+ */
772
+
773
+ escape : function(html) {
774
+ return html.toString().
775
+ replace(/&/gmi, '&amp;').
776
+ replace(/"/gmi, '&quot;').
777
+ replace(/>/gmi, '&gt;').
778
+ replace(/</gmi, '&lt;')
779
+ },
780
+
781
+ /**
782
+ * Perform an assertion without reporting.
783
+ *
784
+ * This method is primarily used for internal
785
+ * matchers in order retain DRYness. May be invoked
786
+ * like below:
787
+ *
788
+ * does('foo', 'eql', 'foo')
789
+ * does([1,2], 'include', 1, 2)
790
+ *
791
+ * @param {mixed} actual
792
+ * @param {string} matcher
793
+ * @param {...} expected
794
+ * @return {mixed}
795
+ * @api private
796
+ */
797
+
798
+ does : function(actual, matcher, expected) {
799
+ var assertion = new JSpec.Assertion(JSpec.matchers[matcher], actual, argumentsToArray(arguments, 2))
800
+ return assertion.run().result
801
+ },
802
+
803
+ /**
804
+ * Perform an assertion.
805
+ *
806
+ * expect(true).to('be', true)
807
+ * expect('foo').not_to('include', 'bar')
808
+ * expect([1, [2]]).to('include', 1, [2])
809
+ *
810
+ * @param {mixed} actual
811
+ * @return {hash}
812
+ * @api public
813
+ */
814
+
815
+ expect : function(actual) {
816
+ assert = function(matcher, args, negate) {
817
+ var expected = []
818
+ for (i = 1; i < args.length; i++) expected.push(args[i])
819
+ assertion = new JSpec.Assertion(matcher, actual, expected, negate)
820
+ if (matcher.defer) assertion.run()
821
+ else JSpec.currentSpec.assertions.push(assertion.run().report())
822
+ return assertion.result
823
+ }
824
+
825
+ to = function(matcher) {
826
+ return assert(matcher, arguments, false)
827
+ }
828
+
829
+ not_to = function(matcher) {
830
+ return assert(matcher, arguments, true)
831
+ }
832
+
833
+ return {
834
+ to : to,
835
+ should : to,
836
+ not_to: not_to,
837
+ should_not : not_to
838
+ }
839
+ },
840
+
841
+ /**
842
+ * Strim whitespace or chars.
843
+ *
844
+ * @param {string} string
845
+ * @param {string} chars
846
+ * @return {string}
847
+ * @api public
848
+ */
849
+
850
+ strip : function(string, chars) {
851
+ return string.
852
+ replace(new RegExp('[' + (chars || '\\s') + ']*$'), '').
853
+ replace(new RegExp('^[' + (chars || '\\s') + ']*'), '')
854
+ },
855
+
856
+ /**
857
+ * Call an iterator callback with arguments a, or b
858
+ * depending on the arity of the callback.
859
+ *
860
+ * @param {function} callback
861
+ * @param {mixed} a
862
+ * @param {mixed} b
863
+ * @return {mixed}
864
+ * @api private
865
+ */
866
+
867
+ callIterator : function(callback, a, b) {
868
+ return callback.length == 1 ? callback(b) : callback(a, b)
869
+ },
870
+
871
+ /**
872
+ * Extend an object with another.
873
+ *
874
+ * @param {object} object
875
+ * @param {object} other
876
+ * @api public
877
+ */
878
+
879
+ extend : function(object, other) {
880
+ each(other, function(property, value){
881
+ object[property] = value
882
+ })
883
+ },
884
+
885
+ /**
886
+ * Iterate an object, invoking the given callback.
887
+ *
888
+ * @param {hash, array, string} object
889
+ * @param {function} callback
890
+ * @return {JSpec}
891
+ * @api public
892
+ */
893
+
894
+ each : function(object, callback) {
895
+ if (typeof object == 'string') object = object.split(' ')
896
+ for (key in object)
897
+ if (object.hasOwnProperty(key))
898
+ callIterator(callback, key, object[key])
899
+ },
900
+
901
+ /**
902
+ * Iterate with memo.
903
+ *
904
+ * @param {hash, array} object
905
+ * @param {object} memo
906
+ * @param {function} callback
907
+ * @return {object}
908
+ * @api public
909
+ */
910
+
911
+ inject : function(object, memo, callback) {
912
+ each(object, function(key, value){
913
+ memo = (callback.length == 2 ?
914
+ callback(memo, value):
915
+ callback(memo, key, value)) ||
916
+ memo
917
+ })
918
+ return memo
919
+ },
920
+
921
+ /**
922
+ * Map callback return values.
923
+ *
924
+ * @param {hash, array} object
925
+ * @param {function} callback
926
+ * @return {array}
927
+ * @api public
928
+ */
929
+
930
+ map : function(object, callback) {
931
+ return inject(object, [], function(memo, key, value){
932
+ memo.push(callIterator(callback, key, value))
933
+ })
934
+ },
935
+
936
+ /**
937
+ * Returns the first matching expression or null.
938
+ *
939
+ * @param {hash, array} object
940
+ * @param {function} callback
941
+ * @return {mixed}
942
+ * @api public
943
+ */
944
+
945
+ any : function(object, callback) {
946
+ return inject(object, null, function(state, key, value){
947
+ return state ? state :
948
+ callIterator(callback, key, value) ?
949
+ value : state
950
+ })
951
+ },
952
+
953
+ /**
954
+ * Returns an array of values collected when the callback
955
+ * given evaluates to true.
956
+ *
957
+ * @param {hash, array} object
958
+ * @return {function} callback
959
+ * @return {array}
960
+ * @api public
961
+ */
962
+
963
+ select : function(object, callback) {
964
+ return inject(object, [], function(memo, key, value){
965
+ if (callIterator(callback, key, value))
966
+ memo.push(value)
967
+ })
968
+ },
969
+
970
+ /**
971
+ * Define matchers.
972
+ *
973
+ * @param {hash} matchers
974
+ * @api public
975
+ */
976
+
977
+ addMatchers : function(matchers) {
978
+ each(matchers, function(name, body){
979
+ JSpec.addMatcher(name, body)
980
+ })
981
+ },
982
+
983
+ /**
984
+ * Define a matcher.
985
+ *
986
+ * @param {string} name
987
+ * @param {hash, function, string} body
988
+ * @api public
989
+ */
990
+
991
+ addMatcher : function(name, body) {
992
+ this.matchers[name] = this.normalizeMatcherMessage(this.normalizeMatcherBody(body))
993
+ this.matchers[name].name = name
994
+ },
995
+
996
+ /**
997
+ * Add a root suite to JSpec.
998
+ *
999
+ * @param {string} description
1000
+ * @param {body} function
1001
+ * @api public
1002
+ */
1003
+
1004
+ describe : function(description, body) {
1005
+ var suite = new JSpec.Suite(description, body)
1006
+ this.allSuites.push(suite)
1007
+ this.suites.push(suite)
1008
+ },
1009
+
1010
+ /**
1011
+ * Return the contents of a function body.
1012
+ *
1013
+ * @param {function} body
1014
+ * @return {string}
1015
+ * @api public
1016
+ */
1017
+
1018
+ contentsOf : function(body) {
1019
+ return body.toString().match(/^[^\{]*{((.*\n*)*)}/m)[1]
1020
+ },
1021
+
1022
+ /**
1023
+ * Evaluate a JSpec capture body.
1024
+ *
1025
+ * @param {function} body
1026
+ * @param {string} errorMessage (optional)
1027
+ * @return {Type}
1028
+ * @api private
1029
+ */
1030
+
1031
+ evalBody : function(body, errorMessage) {
1032
+ var dsl = this.DSL || this.DSLs.snake
1033
+ var matchers = this.matchers
1034
+ var context = this.context || this.defaultContext
1035
+ var contents = this.contentsOf(body)
1036
+ try { eval('with (dsl){ with (context) { with (matchers) { ' + contents + ' }}}') }
1037
+ catch(e) { error(errorMessage, e) }
1038
+ },
1039
+
1040
+ /**
1041
+ * Pre-process a string of JSpec.
1042
+ *
1043
+ * @param {string} input
1044
+ * @return {string}
1045
+ * @api private
1046
+ */
1047
+
1048
+ preprocess : function(input) {
1049
+ return input.
1050
+ replace(/describe (.*?)$/gm, 'describe($1, function(){').
1051
+ replace(/ it (.*?)$/gm, ' it($1, function(){').
1052
+ replace(/^(?: *)(before_each|after_each|before|after)(?= |\n|$)/gm, 'JSpec.currentSuite.addHook("$1", function(){').
1053
+ replace(/end(?= |\n|$)/gm, '});').
1054
+ replace(/-\{/g, 'function(){').
1055
+ replace(/(\d+)\.\.(\d+)/g, function(_, a, b){ return range(a, b) }).
1056
+ replace(/\.should([_\.]not)?[_\.](\w+)(?: |$)(.*)$/gm, '.should$1_$2($3)').
1057
+ replace(/([\/ ]*)(.+?)\.(should(?:[_\.]not)?)[_\.](\w+)\((.*)\)$/gm, '$1 expect($2).$3($4, $5)').
1058
+ replace(/, \)/gm, ')').
1059
+ replace(/should\.not/gm, 'should_not')
1060
+ },
1061
+
1062
+ /**
1063
+ * Create a range string which can be evaluated to a native array.
1064
+ *
1065
+ * @param {int} start
1066
+ * @param {int} end
1067
+ * @return {string}
1068
+ * @api public
1069
+ */
1070
+
1071
+ range : function(start, end) {
1072
+ var current = parseInt(start), end = parseInt(end), values = [current]
1073
+ if (end > current) while (++current <= end) values.push(current)
1074
+ else while (--current >= end) values.push(current)
1075
+ return '[' + values + ']'
1076
+ },
1077
+
1078
+ /**
1079
+ * Call _callback_ when all specs have finished.
1080
+ *
1081
+ * @param {function} callback
1082
+ * @api public
1083
+ */
1084
+
1085
+ whenFinished : function(callback) {
1086
+ if (this.stats.specsFinished >= this.stats.specs) callback()
1087
+ else setTimeout(function(){ JSpec.whenFinished(callback) }, 50)
1088
+ },
1089
+
1090
+ /**
1091
+ * Report on the results.
1092
+ *
1093
+ * @api public
1094
+ */
1095
+
1096
+ report : function() {
1097
+ this.whenFinished(function() {
1098
+ JSpec.options.formatter ?
1099
+ new JSpec.options.formatter(JSpec, JSpec.options):
1100
+ new JSpec.formatters.DOM(JSpec, JSpec.options)
1101
+ })
1102
+ },
1103
+
1104
+ /**
1105
+ * Run the spec suites. Options are merged
1106
+ * with JSpec options when present.
1107
+ *
1108
+ * @param {hash} options
1109
+ * @return {JSpec}
1110
+ * @api public
1111
+ */
1112
+
1113
+ run : function(options) {
1114
+ if (options) extend(this.options, options)
1115
+ if (option('profile')) console.group('Profile')
1116
+ each(this.suites, function(suite) { JSpec.runSuite(suite) })
1117
+ if (option('profile')) console.groupEnd()
1118
+ return this
1119
+ },
1120
+
1121
+ /**
1122
+ * When the current spec's wait duration has passed
1123
+ * the _callback_ will be called.
1124
+ *
1125
+ * @param {function} callback
1126
+ * @api public
1127
+ */
1128
+
1129
+ whenCurrentSpecIsFinished : function(callback) {
1130
+ if (this.currentSpec && this.currentSpec.wait)
1131
+ setTimeout(callback, this.currentSpec.wait)
1132
+ else callback()
1133
+ },
1134
+
1135
+ /**
1136
+ * Run a suite.
1137
+ *
1138
+ * @param {Suite} suite
1139
+ * @api public
1140
+ */
1141
+
1142
+ runSuite : function(suite) {
1143
+ this.currentSuite = suite
1144
+ this.evalBody(suite.body)
1145
+ suite.ran = true
1146
+ suite.hook('before')
1147
+ each(suite.specs, function(spec) {
1148
+ JSpec.whenCurrentSpecIsFinished(function(){
1149
+ suite.hook('before_each')
1150
+ JSpec.runSpec(spec)
1151
+ suite.hook('after_each')
1152
+ })
1153
+ })
1154
+ if (suite.hasSuites()) {
1155
+ each(suite.suites, function(suite) {
1156
+ JSpec.runSuite(suite)
1157
+ })
1158
+ }
1159
+ suite.hook('after')
1160
+ this.stats.suitesFinished++
1161
+ },
1162
+
1163
+ /**
1164
+ * Report a failure for the current spec.
1165
+ *
1166
+ * @param {string} message
1167
+ * @api public
1168
+ */
1169
+
1170
+ fail : function(message) {
1171
+ JSpec.currentSpec.assertions.push({ passed : false, message : message })
1172
+ JSpec.stats.failures++
1173
+ },
1174
+
1175
+ /**
1176
+ * Run a spec.
1177
+ *
1178
+ * @param {Spec} spec
1179
+ * @api public
1180
+ */
1181
+
1182
+ runSpec : function(spec) {
1183
+ this.currentSpec = spec
1184
+ if (option('profile')) console.time(spec.description)
1185
+ try { this.evalBody(spec.body) }
1186
+ catch (e) { fail(e) }
1187
+ spec.runDeferredAssertions()
1188
+ if (option('profile')) console.timeEnd(spec.description)
1189
+ this.stats.specsFinished++
1190
+ this.stats.assertions += spec.assertions.length
1191
+ },
1192
+
1193
+ /**
1194
+ * Require a dependency, with optional message.
1195
+ *
1196
+ * @param {string} dependency
1197
+ * @param {string} message (optional)
1198
+ * @api public
1199
+ */
1200
+
1201
+ requires : function(dependency, message) {
1202
+ try { eval(dependency) }
1203
+ catch (e) { error('JSpec depends on ' + dependency + ' ' + message) }
1204
+ },
1205
+
1206
+ /**
1207
+ * Query against the current query strings keys
1208
+ * or the queryString specified.
1209
+ *
1210
+ * @param {string} key
1211
+ * @param {string} queryString
1212
+ * @return {string, null}
1213
+ * @api private
1214
+ */
1215
+
1216
+ query : function(key, queryString) {
1217
+ var queryString = (queryString || (main.location ? main.location.search : null) || '').substring(1)
1218
+ return inject(queryString.split('&'), null, function(value, pair){
1219
+ parts = pair.split('=')
1220
+ return parts[0] == key ? parts[1].replace(/%20|\+/gmi, ' ') : value
1221
+ })
1222
+ },
1223
+
1224
+ /**
1225
+ * Throw a JSpec related error.
1226
+ *
1227
+ * @param {string} message
1228
+ * @param {Exception} e
1229
+ * @api public
1230
+ */
1231
+
1232
+ error : function(message, e) {
1233
+ throw (message ? message : '') + e.toString() +
1234
+ (e.line ? ' near line ' + e.line : '')
1235
+ },
1236
+
1237
+ /**
1238
+ * Ad-hoc POST request for JSpec server usage.
1239
+ *
1240
+ * @param {string} url
1241
+ * @param {string} data
1242
+ * @api private
1243
+ */
1244
+
1245
+ post : function(url, data) {
1246
+ var request = this.xhr()
1247
+ request.open('POST', url, false)
1248
+ request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
1249
+ request.send(data)
1250
+ },
1251
+
1252
+ /**
1253
+ * Report back to server with statistics.
1254
+ *
1255
+ * @api private
1256
+ */
1257
+
1258
+ reportToServer : function() {
1259
+ this.whenFinished(function(){
1260
+ JSpec.post('http://localhost:4444', 'passes=' + JSpec.stats.passes + '&failures=' + JSpec.stats.failures)
1261
+ if ('close' in main) main.close()
1262
+ })
1263
+ },
1264
+
1265
+ /**
1266
+ * Instantiate an XMLHttpRequest.
1267
+ *
1268
+ * @return {ActiveXObject, XMLHttpRequest}
1269
+ * @api private
1270
+ */
1271
+
1272
+ xhr : function() {
1273
+ return 'ActiveXObject' in main ?
1274
+ new ActiveXObject("Microsoft.XMLHTTP"):
1275
+ new XMLHttpRequest()
1276
+ },
1277
+
1278
+ /**
1279
+ * Check for HTTP request support.
1280
+ *
1281
+ * @return {bool}
1282
+ * @api private
1283
+ */
1284
+
1285
+ hasXhr : function() {
1286
+ return 'XMLHttpRequest' in main || 'ActiveXObject' in main
1287
+ },
1288
+
1289
+ /**
1290
+ * Load a files contents.
1291
+ *
1292
+ * @param {string} file
1293
+ * @return {string}
1294
+ * @api public
1295
+ */
1296
+
1297
+ load : function(file) {
1298
+ if (this.hasXhr()) {
1299
+ var request = this.xhr()
1300
+ request.open('GET', file, false)
1301
+ request.send(null)
1302
+ if (request.readyState == 4) return request.responseText
1303
+ }
1304
+ else if ('readFile' in main)
1305
+ return readFile(file)
1306
+ else
1307
+ error('cannot load ' + file)
1308
+ },
1309
+
1310
+ /**
1311
+ * Load, pre-process, and evaluate a file.
1312
+ *
1313
+ * @param {string} file
1314
+ * @param {JSpec}
1315
+ * @api public
1316
+ */
1317
+
1318
+ exec : function(file) {
1319
+ eval('with (JSpec){' + this.preprocess(this.load(file)) + '}')
1320
+ return this
1321
+ }
1322
+ }
1323
+
1324
+ // --- Utility functions
1325
+
1326
+ var main = this
1327
+ var map = JSpec.map
1328
+ var any = JSpec.any
1329
+ var find = JSpec.any
1330
+ var last = JSpec.last
1331
+ var fail = JSpec.fail
1332
+ var range = JSpec.range
1333
+ var each = JSpec.each
1334
+ var option = JSpec.option
1335
+ var inject = JSpec.inject
1336
+ var select = JSpec.select
1337
+ var error = JSpec.error
1338
+ var escape = JSpec.escape
1339
+ var extend = JSpec.extend
1340
+ var puts = JSpec.puts
1341
+ var hash = JSpec.hash
1342
+ var query = JSpec.query
1343
+ var strip = JSpec.strip
1344
+ var color = JSpec.color
1345
+ var does = JSpec.does
1346
+ var addMatchers = JSpec.addMatchers
1347
+ var callIterator = JSpec.callIterator
1348
+ var argumentsToArray = JSpec.argumentsToArray
1349
+ if (!main.setTimeout) main.setTimeout = function(callback){ callback() }
1350
+
1351
+ // --- Matchers
1352
+
1353
+ addMatchers({
1354
+ equal : "===",
1355
+ be : "alias equal",
1356
+ be_greater_than : ">",
1357
+ be_less_than : "<",
1358
+ be_at_least : ">=",
1359
+ be_at_most : "<=",
1360
+ be_a : "actual.constructor == expected",
1361
+ be_an : "alias be_a",
1362
+ be_an_instance_of : "actual instanceof expected",
1363
+ be_null : "actual == null",
1364
+ be_empty : "actual.length == 0",
1365
+ be_true : "actual == true",
1366
+ be_false : "actual == false",
1367
+ be_type : "typeof actual == expected",
1368
+ match : "typeof actual == 'string' ? actual.match(expected) : false",
1369
+ respond_to : "typeof actual[expected] == 'function'",
1370
+ have_length : "actual.length == expected",
1371
+ be_within : "actual >= expected[0] && actual <= last(expected)",
1372
+ have_length_within : "actual.length >= expected[0] && actual.length <= last(expected)",
1373
+
1374
+ eql : function(actual, expected) {
1375
+ return actual.constructor == Array ||
1376
+ actual instanceof Object ?
1377
+ hash(actual) == hash(expected):
1378
+ actual == expected
1379
+ },
1380
+
1381
+ receive : { defer : true, match : function(actual, method, times) {
1382
+ proxy = new JSpec.ProxyAssertion(actual, method, times)
1383
+ JSpec.currentSpec.assertions.push(proxy)
1384
+ return proxy
1385
+ }},
1386
+
1387
+ include : function(actual) {
1388
+ for (state = true, i = 1; i < arguments.length; i++) {
1389
+ arg = arguments[i]
1390
+ switch (actual.constructor) {
1391
+ case String:
1392
+ case Number:
1393
+ case RegExp:
1394
+ case Function:
1395
+ state = actual.toString().match(arg.toString())
1396
+ break
1397
+
1398
+ case Object:
1399
+ state = arg in actual
1400
+ break
1401
+
1402
+ case Array:
1403
+ state = any(actual, function(value){ return hash(value) == hash(arg) })
1404
+ break
1405
+ }
1406
+ if (!state) return false
1407
+ }
1408
+ return true
1409
+ },
1410
+
1411
+ throw_error : { match : function(actual, expected) {
1412
+ try { actual() }
1413
+ catch (e) {
1414
+ this.e = e
1415
+ if (expected == undefined) return true
1416
+ switch (expected.constructor) {
1417
+ case RegExp : return expected.test(e)
1418
+ case Function : return e instanceof expected
1419
+ case String : return expected == e.toString()
1420
+ }
1421
+ }
1422
+ }, message : function(actual, expected, negate) {
1423
+ // TODO: fix when expected is ONLY expected values
1424
+ expected = (function(){
1425
+ if (expected[1] == undefined) return 'exception'
1426
+ switch (expected[1].constructor) {
1427
+ case RegExp : return 'exception matching ' + puts(expected[1])
1428
+ case Function : return 'instance of ' + expected[1].name
1429
+ case String : return 'exception of ' + puts(expected[1])
1430
+ }
1431
+ })()
1432
+ return 'expected ' + (negate ? 'no ' : 'an ' ) + expected +
1433
+ ' to be thrown, but ' + (this.e ? 'got ' + puts(this.e) : 'nothing was')
1434
+ }},
1435
+
1436
+ have : function(actual, length, property) {
1437
+ return actual[property].length == length
1438
+ },
1439
+
1440
+ have_at_least : function(actual, length, property) {
1441
+ return actual[property].length >= length
1442
+ },
1443
+
1444
+ have_at_most :function(actual, length, property) {
1445
+ return actual[property].length <= length
1446
+ },
1447
+
1448
+ have_within : function(actual, range, property) {
1449
+ length = actual[property].length
1450
+ return length >= range.shift() && length <= range.pop()
1451
+ },
1452
+
1453
+ have_prop : function(actual, property, value) {
1454
+ return actual[property] == null ||
1455
+ actual[property] instanceof Function ? false:
1456
+ value == null ? true:
1457
+ does(actual[property], 'eql', value)
1458
+ },
1459
+
1460
+ have_property : function(actual, property, value) {
1461
+ return actual[property] == null ||
1462
+ actual[property] instanceof Function ? false:
1463
+ value == null ? true:
1464
+ value === actual[property]
1465
+ }
1466
+ })
1467
+
1468
+ })()