jspec-steventux 3.3.2

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 (74) hide show
  1. data/History.md +763 -0
  2. data/Manifest +73 -0
  3. data/README.md +974 -0
  4. data/Rakefile +44 -0
  5. data/bin/jspec +178 -0
  6. data/jspec-steventux.gemspec +44 -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 +149 -0
  14. data/lib/jspec.growl.js +115 -0
  15. data/lib/jspec.jquery.js +72 -0
  16. data/lib/jspec.js +1756 -0
  17. data/lib/jspec.shell.js +39 -0
  18. data/lib/jspec.timers.js +90 -0
  19. data/lib/jspec.xhr.js +195 -0
  20. data/spec/commands/example_command.rb +19 -0
  21. data/spec/dom.html +33 -0
  22. data/spec/fixtures/test.html +1 -0
  23. data/spec/fixtures/test.json +1 -0
  24. data/spec/fixtures/test.xml +5 -0
  25. data/spec/node.js +17 -0
  26. data/spec/rhino.js +23 -0
  27. data/spec/ruby/bin/init_spec.rb +101 -0
  28. data/spec/ruby/bin/install_spec.rb +142 -0
  29. data/spec/ruby/bin/run_spec.rb +0 -0
  30. data/spec/ruby/bin/shell_spec.rb +13 -0
  31. data/spec/ruby/bin/spec_helper.rb +8 -0
  32. data/spec/ruby/bin/update_spec.rb +72 -0
  33. data/spec/server.html +29 -0
  34. data/spec/server.rb +2 -0
  35. data/spec/support/env.js +10118 -0
  36. data/spec/support/jquery.js +4376 -0
  37. data/spec/unit/helpers.js +64 -0
  38. data/spec/unit/spec.fixtures.js +17 -0
  39. data/spec/unit/spec.grammar-less.js +34 -0
  40. data/spec/unit/spec.grammar.js +241 -0
  41. data/spec/unit/spec.jquery.js +178 -0
  42. data/spec/unit/spec.jquery.xhr.js +84 -0
  43. data/spec/unit/spec.js +187 -0
  44. data/spec/unit/spec.matchers.js +577 -0
  45. data/spec/unit/spec.modules.js +51 -0
  46. data/spec/unit/spec.shared-behaviors.js +80 -0
  47. data/spec/unit/spec.utils.js +346 -0
  48. data/spec/unit/spec.xhr.js +157 -0
  49. data/src/browsers.rb +294 -0
  50. data/src/helpers.rb +67 -0
  51. data/src/installables.rb +229 -0
  52. data/src/project.rb +341 -0
  53. data/src/routes.rb +57 -0
  54. data/src/server.rb +99 -0
  55. data/support/js.jar +0 -0
  56. data/templates/default/History.md +5 -0
  57. data/templates/default/Readme.md +29 -0
  58. data/templates/default/lib/yourlib.js +2 -0
  59. data/templates/default/spec/commands/example_command.rb +19 -0
  60. data/templates/default/spec/dom.html +22 -0
  61. data/templates/default/spec/node.js +10 -0
  62. data/templates/default/spec/rhino.js +10 -0
  63. data/templates/default/spec/server.html +18 -0
  64. data/templates/default/spec/server.rb +4 -0
  65. data/templates/default/spec/unit/spec.helper.js +0 -0
  66. data/templates/default/spec/unit/spec.js +8 -0
  67. data/templates/rails/commands/example_commands.rb +19 -0
  68. data/templates/rails/dom.html +22 -0
  69. data/templates/rails/rhino.js +10 -0
  70. data/templates/rails/server.html +18 -0
  71. data/templates/rails/server.rb +4 -0
  72. data/templates/rails/unit/spec.helper.js +0 -0
  73. data/templates/rails/unit/spec.js +8 -0
  74. metadata +185 -0
@@ -0,0 +1,72 @@
1
+
2
+ // JSpec - jQuery - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
3
+
4
+ JSpec
5
+ .requires('jQuery', 'when using jspec.jquery.js')
6
+ .include({
7
+ name: 'jQuery',
8
+
9
+ // --- Initialize
10
+
11
+ init : function() {
12
+ jQuery.ajaxSetup({ async: false })
13
+ },
14
+
15
+ // --- Utilities
16
+
17
+ utilities : {
18
+ element: jQuery,
19
+ elements: jQuery,
20
+ sandbox : function() {
21
+ return jQuery('<div class="sandbox"></div>')
22
+ }
23
+ },
24
+
25
+ // --- Matchers
26
+
27
+ matchers : {
28
+ have_tag : "jQuery(expected, actual).length === 1",
29
+ have_one : "alias have_tag",
30
+ have_tags : "jQuery(expected, actual).length > 1",
31
+ have_many : "alias have_tags",
32
+ have_any : "alias have_tags",
33
+ have_child : "jQuery(actual).children(expected).length === 1",
34
+ have_children : "jQuery(actual).children(expected).length > 1",
35
+ have_text : "jQuery(actual).text() === expected",
36
+ have_value : "jQuery(actual).val() === expected",
37
+ be_enabled : "!jQuery(actual).attr('disabled')",
38
+ have_class : "jQuery(actual).hasClass(expected)",
39
+
40
+ be_visible : function(actual) {
41
+ return jQuery(actual).css('display') != 'none' &&
42
+ jQuery(actual).css('visibility') != 'hidden' &&
43
+ jQuery(actual).attr('type') != 'hidden'
44
+ },
45
+
46
+ be_hidden : function(actual) {
47
+ return !JSpec.does(actual, 'be_visible')
48
+ },
49
+
50
+ have_classes : function(actual) {
51
+ return !JSpec.any(JSpec.toArray(arguments, 1), function(arg){
52
+ return !JSpec.does(actual, 'have_class', arg)
53
+ })
54
+ },
55
+
56
+ have_attr : function(actual, attr, value) {
57
+ return value ? jQuery(actual).attr(attr) == value:
58
+ jQuery(actual).attr(attr)
59
+ },
60
+
61
+ 'be disabled selected checked' : function(attr) {
62
+ return 'jQuery(actual).attr("' + attr + '")'
63
+ },
64
+
65
+ 'have type id title alt href src sel rev name target' : function(attr) {
66
+ return function(actual, value) {
67
+ return JSpec.does(actual, 'have_attr', attr, value)
68
+ }
69
+ }
70
+ }
71
+ })
72
+
@@ -0,0 +1,1756 @@
1
+
2
+ // JSpec - Core - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
3
+
4
+ ;(function(){
5
+
6
+ JSpec = {
7
+ version : '3.3.2',
8
+ assert : true,
9
+ cache : {},
10
+ suites : [],
11
+ modules : [],
12
+ allSuites : [],
13
+ matchers : {},
14
+ stubbed : [],
15
+ options : {},
16
+ request : 'XMLHttpRequest' in this ? XMLHttpRequest : null,
17
+ stats : { specs: 0, assertions: 0, failures: 0, passes: 0, specsFinished: 0, suitesFinished: 0 },
18
+
19
+ /**
20
+ * Default context in which bodies are evaluated.
21
+ *
22
+ * Replace context simply by setting JSpec.context
23
+ * to your own like below:
24
+ *
25
+ * JSpec.context = { foo : 'bar' }
26
+ *
27
+ * Contexts can be changed within any body, this can be useful
28
+ * in order to provide specific helper methods to specific suites.
29
+ *
30
+ * To reset (usually in after hook) simply set to null like below:
31
+ *
32
+ * JSpec.context = null
33
+ *
34
+ */
35
+
36
+ defaultContext : {
37
+
38
+ /**
39
+ * Return an object used for proxy assertions.
40
+ * This object is used to indicate that an object
41
+ * should be an instance of _object_, not the constructor
42
+ * itself.
43
+ *
44
+ * @param {function} constructor
45
+ * @return {hash}
46
+ * @api public
47
+ */
48
+
49
+ an_instance_of : function(constructor) {
50
+ return { an_instance_of : constructor }
51
+ },
52
+
53
+ /**
54
+ * Load fixture at _path_.
55
+ *
56
+ * Fixtures are resolved as:
57
+ *
58
+ * - <path>
59
+ * - <path>.html
60
+ *
61
+ * @param {string} path
62
+ * @return {string}
63
+ * @api public
64
+ */
65
+
66
+ fixture : function(path) {
67
+ if (JSpec.cache[path]) return JSpec.cache[path]
68
+ return JSpec.cache[path] =
69
+ JSpec.tryLoading(JSpec.options.fixturePath + '/' + path) ||
70
+ JSpec.tryLoading(JSpec.options.fixturePath + '/' + path + '.html')
71
+ }
72
+ },
73
+
74
+ // --- Objects
75
+
76
+ reporters : {
77
+
78
+ /**
79
+ * Report to server.
80
+ *
81
+ * Options:
82
+ * - uri specific uri to report to.
83
+ * - verbose weither or not to output messages
84
+ * - failuresOnly output failure messages only
85
+ *
86
+ * @api public
87
+ */
88
+
89
+ Server : function(results, options) {
90
+ var uri = options.uri || 'http://' + window.location.host + '/results'
91
+ JSpec.post(uri, {
92
+ stats: JSpec.stats,
93
+ options: options,
94
+ results: map(results.allSuites, function(suite) {
95
+ if (suite.hasSpecs())
96
+ return {
97
+ description: suite.description,
98
+ specs: map(suite.specs, function(spec) {
99
+ return {
100
+ description: spec.description,
101
+ message: !spec.passed() ? spec.failure().message : null,
102
+ status: spec.requiresImplementation() ? 'pending' :
103
+ spec.passed() ? 'pass' :
104
+ 'fail',
105
+ assertions: map(spec.assertions, function(assertion){
106
+ return {
107
+ passed: assertion.passed
108
+ }
109
+ })
110
+ }
111
+ })
112
+ }
113
+ })
114
+ })
115
+ if ('close' in main) main.close()
116
+ },
117
+
118
+ /**
119
+ * Default reporter, outputting to the DOM.
120
+ *
121
+ * Options:
122
+ * - reportToId id of element to output reports to, defaults to 'jspec'
123
+ * - failuresOnly displays only suites with failing specs
124
+ *
125
+ * @api public
126
+ */
127
+
128
+ DOM : function(results, options) {
129
+ var id = option('reportToId') || 'jspec',
130
+ report = document.getElementById(id),
131
+ failuresOnly = option('failuresOnly'),
132
+ classes = results.stats.failures ? 'has-failures' : ''
133
+ if (!report) throw 'JSpec requires the element #' + id + ' to output its reports'
134
+
135
+ function bodyContents(body) {
136
+ return JSpec.
137
+ escape(JSpec.contentsOf(body)).
138
+ replace(/^ */gm, function(a){ return (new Array(Math.round(a.length / 3))).join(' ') }).
139
+ replace(/\r\n|\r|\n/gm, '<br/>')
140
+ }
141
+
142
+ report.innerHTML = '<div id="jspec-report" class="' + classes + '"><div class="heading"> \
143
+ <span class="passes">Passes: <em>' + results.stats.passes + '</em></span> \
144
+ <span class="failures">Failures: <em>' + results.stats.failures + '</em></span> \
145
+ <span class="passes">Duration: <em>' + results.duration + '</em> ms</span> \
146
+ </div><table class="suites">' + map(results.allSuites, function(suite) {
147
+ var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
148
+ if (displaySuite && suite.hasSpecs())
149
+ return '<tr class="description"><td colspan="2">' + escape(suite.description) + '</td></tr>' +
150
+ map(suite.specs, function(i, spec) {
151
+ return '<tr class="' + (i % 2 ? 'odd' : 'even') + '">' +
152
+ (spec.requiresImplementation() ?
153
+ '<td class="requires-implementation" colspan="2">' + escape(spec.description) + '</td>' :
154
+ (spec.passed() && !failuresOnly) ?
155
+ '<td class="pass">' + escape(spec.description)+ '</td><td>' + spec.assertionsGraph() + '</td>' :
156
+ !spec.passed() ?
157
+ '<td class="fail">' + escape(spec.description) +
158
+ map(spec.failures(), function(a){ return '<em>' + escape(a.message) + '</em>' }).join('') +
159
+ '</td><td>' + spec.assertionsGraph() + '</td>' :
160
+ '') +
161
+ '<tr class="body"><td colspan="2"><pre>' + bodyContents(spec.body) + '</pre></td></tr>'
162
+ }).join('') + '</tr>'
163
+ }).join('') + '</table></div>'
164
+ },
165
+
166
+ /**
167
+ * Terminal reporter.
168
+ *
169
+ * @api public
170
+ */
171
+
172
+ Terminal : function(results, options) {
173
+ var failuresOnly = option('failuresOnly')
174
+ print(color("\n Passes: ", 'bold') + color(results.stats.passes, 'green') +
175
+ color(" Failures: ", 'bold') + color(results.stats.failures, 'red') +
176
+ color(" Duration: ", 'bold') + color(results.duration, 'green') + " ms \n")
177
+
178
+ function indent(string) {
179
+ return string.replace(/^(.)/gm, ' $1')
180
+ }
181
+
182
+ each(results.allSuites, function(suite) {
183
+ var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
184
+ if (displaySuite && suite.hasSpecs()) {
185
+ print(color(' ' + suite.description, 'bold'))
186
+ each(suite.specs, function(spec){
187
+ var assertionsGraph = inject(spec.assertions, '', function(graph, assertion){
188
+ return graph + color('.', assertion.passed ? 'green' : 'red')
189
+ })
190
+ if (spec.requiresImplementation())
191
+ print(color(' ' + spec.description, 'blue') + assertionsGraph)
192
+ else if (spec.passed() && !failuresOnly)
193
+ print(color(' ' + spec.description, 'green') + assertionsGraph)
194
+ else if (!spec.passed())
195
+ print(color(' ' + spec.description, 'red') + assertionsGraph +
196
+ "\n" + indent(map(spec.failures(), function(a){ return a.message }).join("\n")) + "\n")
197
+ })
198
+ print("")
199
+ }
200
+ })
201
+
202
+ quit(results.stats.failures)
203
+ }
204
+ },
205
+
206
+ Assertion : function(matcher, actual, expected, negate) {
207
+ extend(this, {
208
+ message: '',
209
+ passed: false,
210
+ actual: actual,
211
+ negate: negate,
212
+ matcher: matcher,
213
+ expected: expected,
214
+
215
+ // Report assertion results
216
+
217
+ report : function() {
218
+ if (JSpec.assert)
219
+ this.passed ? JSpec.stats.passes++ : JSpec.stats.failures++
220
+ return this
221
+ },
222
+
223
+ // Run the assertion
224
+
225
+ run : function() {
226
+ // TODO: remove unshifting
227
+ expected.unshift(actual)
228
+ this.result = matcher.match.apply(this, expected)
229
+ this.passed = negate ? !this.result : this.result
230
+ if (!this.passed) this.message = matcher.message.call(this, actual, expected, negate, matcher.name)
231
+ return this
232
+ }
233
+ })
234
+ },
235
+
236
+ ProxyAssertion : function(object, method, times, negate) {
237
+ var self = this
238
+ var old = object[method]
239
+
240
+ // Proxy
241
+
242
+ object[method] = function(){
243
+ args = toArray(arguments)
244
+ result = old.apply(object, args)
245
+ self.calls.push({ args : args, result : result })
246
+ return result
247
+ }
248
+
249
+ // Times
250
+
251
+ this.times = {
252
+ once : 1,
253
+ twice : 2
254
+ }[times] || times || 1
255
+
256
+ extend(this, {
257
+ calls: [],
258
+ message: '',
259
+ defer: true,
260
+ passed: false,
261
+ negate: negate,
262
+ object: object,
263
+ method: method,
264
+
265
+ // Proxy return value
266
+
267
+ and_return : function(result) {
268
+ this.expectedResult = result
269
+ return this
270
+ },
271
+
272
+ // Proxy arguments passed
273
+
274
+ with_args : function() {
275
+ this.expectedArgs = toArray(arguments)
276
+ return this
277
+ },
278
+
279
+ // Check if any calls have failing results
280
+
281
+ anyResultsFail : function() {
282
+ return any(this.calls, function(call){
283
+ return self.expectedResult.an_instance_of ?
284
+ call.result.constructor != self.expectedResult.an_instance_of:
285
+ !equal(self.expectedResult, call.result)
286
+ })
287
+ },
288
+
289
+ // Check if any calls have passing results
290
+
291
+ anyResultsPass : function() {
292
+ return any(this.calls, function(call){
293
+ return self.expectedResult.an_instance_of ?
294
+ call.result.constructor == self.expectedResult.an_instance_of:
295
+ equal(self.expectedResult, call.result)
296
+ })
297
+ },
298
+
299
+ // Return the passing result
300
+
301
+ passingResult : function() {
302
+ return this.anyResultsPass().result
303
+ },
304
+
305
+ // Return the failing result
306
+
307
+ failingResult : function() {
308
+ return this.anyResultsFail().result
309
+ },
310
+
311
+ // Check if any arguments fail
312
+
313
+ anyArgsFail : function() {
314
+ return any(this.calls, function(call){
315
+ return any(self.expectedArgs, function(i, arg){
316
+ if (arg == null) return call.args[i] == null
317
+ return arg.an_instance_of ?
318
+ call.args[i].constructor != arg.an_instance_of:
319
+ !equal(arg, call.args[i])
320
+
321
+ })
322
+ })
323
+ },
324
+
325
+ // Check if any arguments pass
326
+
327
+ anyArgsPass : function() {
328
+ return any(this.calls, function(call){
329
+ return any(self.expectedArgs, function(i, arg){
330
+ return arg.an_instance_of ?
331
+ call.args[i].constructor == arg.an_instance_of:
332
+ equal(arg, call.args[i])
333
+
334
+ })
335
+ })
336
+ },
337
+
338
+ // Return the passing args
339
+
340
+ passingArgs : function() {
341
+ return this.anyArgsPass().args
342
+ },
343
+
344
+ // Return the failing args
345
+
346
+ failingArgs : function() {
347
+ return this.anyArgsFail().args
348
+ },
349
+
350
+ // Report assertion results
351
+
352
+ report : function() {
353
+ if (JSpec.assert)
354
+ this.passed ? ++JSpec.stats.passes : ++JSpec.stats.failures
355
+ return this
356
+ },
357
+
358
+ // Run the assertion
359
+
360
+ run : function() {
361
+ var methodString = 'expected ' + object.toString() + '.' + method + '()' + (negate ? ' not' : '' )
362
+
363
+ function times(n) {
364
+ return n > 2 ? n + ' times' : { 1: 'once', 2: 'twice' }[n]
365
+ }
366
+
367
+ if (this.expectedResult != null && (negate ? this.anyResultsPass() : this.anyResultsFail()))
368
+ this.message = methodString + ' to return ' + puts(this.expectedResult) +
369
+ ' but ' + (negate ? 'it did' : 'got ' + puts(this.failingResult()))
370
+
371
+ if (this.expectedArgs && (negate ? !this.expectedResult && this.anyArgsPass() : this.anyArgsFail()))
372
+ this.message = methodString + ' to be called with ' + puts.apply(this, this.expectedArgs) +
373
+ ' but was' + (negate ? '' : ' called with ' + puts.apply(this, this.failingArgs()))
374
+
375
+ if (negate ? !this.expectedResult && !this.expectedArgs && this.calls.length >= this.times : this.calls.length != this.times)
376
+ this.message = methodString + ' to be called ' + times(this.times) +
377
+ ', but ' + (this.calls.length == 0 ? ' was not called' : ' was called ' + times(this.calls.length))
378
+
379
+ if (!this.message.length)
380
+ this.passed = true
381
+
382
+ return this
383
+ }
384
+ })
385
+ },
386
+
387
+ /**
388
+ * Specification Suite block object.
389
+ *
390
+ * @param {string} description
391
+ * @param {function} body
392
+ * @api private
393
+ */
394
+
395
+ Suite : function(description, body) {
396
+ var self = this
397
+ extend(this, {
398
+ body: body,
399
+ description: description,
400
+ suites: [],
401
+ specs: [],
402
+ ran: false,
403
+ hooks: { 'before' : [], 'after' : [], 'before_each' : [], 'after_each' : [] },
404
+
405
+ // Add a spec to the suite
406
+
407
+ addSpec : function(description, body) {
408
+ var spec = new JSpec.Spec(description, body)
409
+ this.specs.push(spec)
410
+ JSpec.stats.specs++ // TODO: abstract
411
+ spec.suite = this
412
+ },
413
+
414
+ // Add a hook to the suite
415
+
416
+ addHook : function(hook, body) {
417
+ this.hooks[hook].push(body)
418
+ },
419
+
420
+ // Add a nested suite
421
+
422
+ addSuite : function(description, body) {
423
+ var suite = new JSpec.Suite(description, body)
424
+ JSpec.allSuites.push(suite)
425
+ suite.name = suite.description
426
+ suite.description = this.description + ' ' + suite.description
427
+ this.suites.push(suite)
428
+ suite.suite = this
429
+ },
430
+
431
+ // Invoke a hook in context to this suite
432
+
433
+ hook : function(hook) {
434
+ if (this.suite) this.suite.hook(hook)
435
+ each(this.hooks[hook], function(body) {
436
+ JSpec.evalBody(body, "Error in hook '" + hook + "', suite '" + self.description + "': ")
437
+ })
438
+ },
439
+
440
+ // Check if nested suites are present
441
+
442
+ hasSuites : function() {
443
+ return this.suites.length
444
+ },
445
+
446
+ // Check if this suite has specs
447
+
448
+ hasSpecs : function() {
449
+ return this.specs.length
450
+ },
451
+
452
+ // Check if the entire suite passed
453
+
454
+ passed : function() {
455
+ return !any(this.specs, function(spec){
456
+ return !spec.passed()
457
+ })
458
+ }
459
+ })
460
+ },
461
+
462
+ /**
463
+ * Specification block object.
464
+ *
465
+ * @param {string} description
466
+ * @param {function} body
467
+ * @api private
468
+ */
469
+
470
+ Spec : function(description, body) {
471
+ extend(this, {
472
+ body: body,
473
+ description: description,
474
+ assertions: [],
475
+
476
+ // Add passing assertion
477
+
478
+ pass : function(message) {
479
+ this.assertions.push({ passed: true, message: message })
480
+ if (JSpec.assert) ++JSpec.stats.passes
481
+ },
482
+
483
+ // Add failing assertion
484
+
485
+ fail : function(message) {
486
+ this.assertions.push({ passed: false, message: message })
487
+ if (JSpec.assert) ++JSpec.stats.failures
488
+ },
489
+
490
+ // Run deferred assertions
491
+
492
+ runDeferredAssertions : function() {
493
+ each(this.assertions, function(assertion){
494
+ if (assertion.defer) assertion.run().report(), hook('afterAssertion', assertion)
495
+ })
496
+ },
497
+
498
+ // Find first failing assertion
499
+
500
+ failure : function() {
501
+ return find(this.assertions, function(assertion){
502
+ return !assertion.passed
503
+ })
504
+ },
505
+
506
+ // Find all failing assertions
507
+
508
+ failures : function() {
509
+ return select(this.assertions, function(assertion){
510
+ return !assertion.passed
511
+ })
512
+ },
513
+
514
+ // Weither or not the spec passed
515
+
516
+ passed : function() {
517
+ return !this.failure()
518
+ },
519
+
520
+ // Weither or not the spec requires implementation (no assertions)
521
+
522
+ requiresImplementation : function() {
523
+ return this.assertions.length == 0
524
+ },
525
+
526
+ // Sprite based assertions graph
527
+
528
+ assertionsGraph : function() {
529
+ return map(this.assertions, function(assertion){
530
+ return '<span class="assertion ' + (assertion.passed ? 'passed' : 'failed') + '"></span>'
531
+ }).join('')
532
+ }
533
+ })
534
+ },
535
+
536
+ Module : function(methods) {
537
+ extend(this, methods)
538
+ },
539
+
540
+ JSON : {
541
+
542
+ /**
543
+ * Generic sequences.
544
+ */
545
+
546
+ meta : {
547
+ '\b' : '\\b',
548
+ '\t' : '\\t',
549
+ '\n' : '\\n',
550
+ '\f' : '\\f',
551
+ '\r' : '\\r',
552
+ '"' : '\\"',
553
+ '\\' : '\\\\'
554
+ },
555
+
556
+ /**
557
+ * Escapable sequences.
558
+ */
559
+
560
+ escapable : /[\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
561
+
562
+ /**
563
+ * JSON encode _object_.
564
+ *
565
+ * @param {mixed} object
566
+ * @return {string}
567
+ * @api private
568
+ */
569
+
570
+ encode : function(object) {
571
+ var self = this
572
+ if (object == undefined || object == null) return 'null'
573
+ if (object === true) return 'true'
574
+ if (object === false) return 'false'
575
+ switch (typeof object) {
576
+ case 'number': return object
577
+ case 'string': return this.escapable.test(object) ?
578
+ '"' + object.replace(this.escapable, function (a) {
579
+ return typeof self.meta[a] === 'string' ? self.meta[a] :
580
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4)
581
+ }) + '"' :
582
+ '"' + object + '"'
583
+ case 'object':
584
+ if (object.constructor == Array)
585
+ return '[' + map(object, function(val){
586
+ return self.encode(val)
587
+ }).join(', ') + ']'
588
+ else if (object)
589
+ return '{' + map(object, function(key, val){
590
+ return self.encode(key) + ':' + self.encode(val)
591
+ }).join(', ') + '}'
592
+ }
593
+ return 'null'
594
+ }
595
+ },
596
+
597
+ // --- DSLs
598
+
599
+ DSLs : {
600
+ snake : {
601
+ expect : function(actual){
602
+ return JSpec.expect(actual)
603
+ },
604
+
605
+ describe : function(description, body) {
606
+ return JSpec.currentSuite.addSuite(description, body)
607
+ },
608
+
609
+ it : function(description, body) {
610
+ return JSpec.currentSuite.addSpec(description, body)
611
+ },
612
+
613
+ before : function(body) {
614
+ return JSpec.currentSuite.addHook('before', body)
615
+ },
616
+
617
+ after : function(body) {
618
+ return JSpec.currentSuite.addHook('after', body)
619
+ },
620
+
621
+ before_each : function(body) {
622
+ return JSpec.currentSuite.addHook('before_each', body)
623
+ },
624
+
625
+ after_each : function(body) {
626
+ return JSpec.currentSuite.addHook('after_each', body)
627
+ },
628
+
629
+ should_behave_like : function(description) {
630
+ return JSpec.shareBehaviorsOf(description)
631
+ }
632
+ }
633
+ },
634
+
635
+ // --- Methods
636
+
637
+ /**
638
+ * Check if _value_ is 'stop'. For use as a
639
+ * utility callback function.
640
+ *
641
+ * @param {mixed} value
642
+ * @return {bool}
643
+ * @api public
644
+ */
645
+
646
+ haveStopped : function(value) {
647
+ return value === 'stop'
648
+ },
649
+
650
+ /**
651
+ * Include _object_ which may be a hash or Module instance.
652
+ *
653
+ * @param {hash, Module} object
654
+ * @return {JSpec}
655
+ * @api public
656
+ */
657
+
658
+ include : function(object) {
659
+ var module = object.constructor == JSpec.Module ? object : new JSpec.Module(object)
660
+ this.modules.push(module)
661
+ if ('init' in module) module.init()
662
+ if ('utilities' in module) extend(this.defaultContext, module.utilities)
663
+ if ('matchers' in module) this.addMatchers(module.matchers)
664
+ if ('reporters' in module) extend(this.reporters, module.reporters)
665
+ if ('DSLs' in module)
666
+ each(module.DSLs, function(name, methods){
667
+ JSpec.DSLs[name] = JSpec.DSLs[name] || {}
668
+ extend(JSpec.DSLs[name], methods)
669
+ })
670
+ return this
671
+ },
672
+
673
+ /**
674
+ * Add a module hook _name_, which is immediately
675
+ * called per module with the _args_ given. An array of
676
+ * hook return values is returned.
677
+ *
678
+ * @param {name} string
679
+ * @param {...} args
680
+ * @return {array}
681
+ * @api private
682
+ */
683
+
684
+ hook : function(name, args) {
685
+ args = toArray(arguments, 1)
686
+ return inject(JSpec.modules, [], function(results, module){
687
+ if (typeof module[name] == 'function')
688
+ results.push(JSpec.evalHook(module, name, args))
689
+ })
690
+ },
691
+
692
+ /**
693
+ * Eval _module_ hook _name_ with _args_. Evaluates in context
694
+ * to the module itself, JSpec, and JSpec.context.
695
+ *
696
+ * @param {Module} module
697
+ * @param {string} name
698
+ * @param {array} args
699
+ * @return {mixed}
700
+ * @api private
701
+ */
702
+
703
+ evalHook : function(module, name, args) {
704
+ hook('evaluatingHookBody', module, name)
705
+ try { return module[name].apply(module, args) }
706
+ catch(e) { error('Error in hook ' + module.name + '.' + name + ': ', e) }
707
+ },
708
+
709
+ /**
710
+ * Same as hook() however accepts only one _arg_ which is
711
+ * considered immutable. This function passes the arg
712
+ * to the first module, then passes the return value of the last
713
+ * module called, to the following module.
714
+ *
715
+ * @param {string} name
716
+ * @param {mixed} arg
717
+ * @return {mixed}
718
+ * @api private
719
+ */
720
+
721
+ hookImmutable : function(name, arg) {
722
+ return inject(JSpec.modules, arg, function(result, module){
723
+ if (typeof module[name] == 'function')
724
+ return JSpec.evalHook(module, name, [result])
725
+ })
726
+ },
727
+
728
+ /**
729
+ * Find a suite by its description or name.
730
+ *
731
+ * @param {string} description
732
+ * @return {Suite}
733
+ * @api private
734
+ */
735
+
736
+ findSuite : function(description) {
737
+ return find(this.allSuites, function(suite){
738
+ return suite.name == description || suite.description == description
739
+ })
740
+ },
741
+
742
+ /**
743
+ * Share behaviors (specs) of the given suite with
744
+ * the current suite.
745
+ *
746
+ * @param {string} description
747
+ * @api public
748
+ */
749
+
750
+ shareBehaviorsOf : function(description) {
751
+ if (suite = this.findSuite(description)) this.copySpecs(suite, this.currentSuite)
752
+ else throw 'failed to share behaviors. ' + puts(description) + ' is not a valid Suite name'
753
+ },
754
+
755
+ /**
756
+ * Copy specs from one suite to another.
757
+ *
758
+ * @param {Suite} fromSuite
759
+ * @param {Suite} toSuite
760
+ * @api public
761
+ */
762
+
763
+ copySpecs : function(fromSuite, toSuite) {
764
+ each(fromSuite.specs, function(spec){
765
+ var newSpec = new Object();
766
+ extend(newSpec, spec);
767
+ newSpec.assertions = [];
768
+ toSuite.specs.push(newSpec);
769
+ })
770
+ },
771
+
772
+ /**
773
+ * Convert arguments to an array.
774
+ *
775
+ * @param {object} arguments
776
+ * @param {int} offset
777
+ * @return {array}
778
+ * @api public
779
+ */
780
+
781
+ toArray : function(arguments, offset) {
782
+ return Array.prototype.slice.call(arguments, offset || 0)
783
+ },
784
+
785
+ /**
786
+ * Return ANSI-escaped colored string.
787
+ *
788
+ * @param {string} string
789
+ * @param {string} color
790
+ * @return {string}
791
+ * @api public
792
+ */
793
+
794
+ color : function(string, color) {
795
+ return "\u001B[" + {
796
+ bold : 1,
797
+ black : 30,
798
+ red : 31,
799
+ green : 32,
800
+ yellow : 33,
801
+ blue : 34,
802
+ magenta : 35,
803
+ cyan : 36,
804
+ white : 37
805
+ }[color] + 'm' + string + "\u001B[0m"
806
+ },
807
+
808
+ /**
809
+ * Default matcher message callback.
810
+ *
811
+ * @api private
812
+ */
813
+
814
+ defaultMatcherMessage : function(actual, expected, negate, name) {
815
+ return 'expected ' + puts(actual) + ' to ' +
816
+ (negate ? 'not ' : '') +
817
+ name.replace(/_/g, ' ') +
818
+ ' ' + (expected.length > 1 ?
819
+ puts.apply(this, expected.slice(1)) :
820
+ '')
821
+ },
822
+
823
+ /**
824
+ * Normalize a matcher message.
825
+ *
826
+ * When no messge callback is present the defaultMatcherMessage
827
+ * will be assigned, will suffice for most matchers.
828
+ *
829
+ * @param {hash} matcher
830
+ * @return {hash}
831
+ * @api public
832
+ */
833
+
834
+ normalizeMatcherMessage : function(matcher) {
835
+ if (typeof matcher.message != 'function')
836
+ matcher.message = this.defaultMatcherMessage
837
+ return matcher
838
+ },
839
+
840
+ /**
841
+ * Normalize a matcher body
842
+ *
843
+ * This process allows the following conversions until
844
+ * the matcher is in its final normalized hash state.
845
+ *
846
+ * - '==' becomes 'actual == expected'
847
+ * - 'actual == expected' becomes 'return actual == expected'
848
+ * - function(actual, expected) { return actual == expected } becomes
849
+ * { match : function(actual, expected) { return actual == expected }}
850
+ *
851
+ * @param {mixed} body
852
+ * @return {hash}
853
+ * @api public
854
+ */
855
+
856
+ normalizeMatcherBody : function(body) {
857
+ switch (body.constructor) {
858
+ case String:
859
+ if (captures = body.match(/^alias (\w+)/)) return JSpec.matchers[last(captures)]
860
+ if (body.length < 4) body = 'actual ' + body + ' expected'
861
+ return { match: function(actual, expected) { return eval(body) }}
862
+
863
+ case Function:
864
+ return { match: body }
865
+
866
+ default:
867
+ return body
868
+ }
869
+ },
870
+
871
+ /**
872
+ * Get option value. This method first checks if
873
+ * the option key has been set via the query string,
874
+ * otherwise returning the options hash value.
875
+ *
876
+ * @param {string} key
877
+ * @return {mixed}
878
+ * @api public
879
+ */
880
+
881
+ option : function(key) {
882
+ return (value = query(key)) !== null ? value :
883
+ JSpec.options[key] || null
884
+ },
885
+
886
+ /**
887
+ * Check if object _a_, is equal to object _b_.
888
+ *
889
+ * @param {object} a
890
+ * @param {object} b
891
+ * @return {bool}
892
+ * @api private
893
+ */
894
+
895
+ equal: function(a, b) {
896
+ if (typeof a != typeof b) return
897
+ if (a === b) return true
898
+ if (a instanceof RegExp)
899
+ return a.toString() === b.toString()
900
+ if (a instanceof Date)
901
+ return Number(a) === Number(b)
902
+ if (typeof a != 'object') return
903
+ if (a.length !== undefined)
904
+ if (a.length !== b.length) return
905
+ else
906
+ for (var i = 0, len = a.length; i < len; ++i)
907
+ if (!equal(a[i], b[i]))
908
+ return
909
+ for (var key in a)
910
+ if (!equal(a[key], b[key]))
911
+ return
912
+ return true
913
+ },
914
+
915
+ /**
916
+ * Return last element of an array.
917
+ *
918
+ * @param {array} array
919
+ * @return {object}
920
+ * @api public
921
+ */
922
+
923
+ last : function(array) {
924
+ return array[array.length - 1]
925
+ },
926
+
927
+ /**
928
+ * Convert object(s) to a print-friend string.
929
+ *
930
+ * @param {...} object
931
+ * @return {string}
932
+ * @api public
933
+ */
934
+
935
+ puts : function(object) {
936
+ if (arguments.length > 1)
937
+ return map(toArray(arguments), function(arg){
938
+ return puts(arg)
939
+ }).join(', ')
940
+ if (object === undefined) return 'undefined'
941
+ if (object === null) return 'null'
942
+ if (object === true) return 'true'
943
+ if (object === false) return 'false'
944
+ if (object.an_instance_of) return 'an instance of ' + object.an_instance_of.name
945
+ if (object.jquery && object.selector.length > 0) return 'selector ' + puts(object.selector)
946
+ if (object.jquery) return object.get(0).outerHTML
947
+ if (object.nodeName) return object.outerHTML
948
+ switch (object.constructor) {
949
+ case Function: return object.name || object
950
+ case String:
951
+ return '"' + object
952
+ .replace(/"/g, '\\"')
953
+ .replace(/\n/g, '\\n')
954
+ .replace(/\t/g, '\\t')
955
+ + '"'
956
+ case Array:
957
+ return inject(object, '[', function(b, v){
958
+ return b + ', ' + puts(v)
959
+ }).replace('[,', '[') + ' ]'
960
+ case Object:
961
+ object.__hit__ = true
962
+ return inject(object, '{', function(b, k, v) {
963
+ if (k == '__hit__') return b
964
+ return b + ', ' + k + ': ' + (v && v.__hit__ ? '<circular reference>' : puts(v))
965
+ }).replace('{,', '{') + ' }'
966
+ default:
967
+ return object.toString()
968
+ }
969
+ },
970
+
971
+ /**
972
+ * Escape HTML.
973
+ *
974
+ * @param {string} html
975
+ * @return {string}
976
+ * @api public
977
+ */
978
+
979
+ escape : function(html) {
980
+ return html.toString()
981
+ .replace(/&/gmi, '&amp;')
982
+ .replace(/"/gmi, '&quot;')
983
+ .replace(/>/gmi, '&gt;')
984
+ .replace(/</gmi, '&lt;')
985
+ },
986
+
987
+ /**
988
+ * Perform an assertion without reporting.
989
+ *
990
+ * This method is primarily used for internal
991
+ * matchers in order retain DRYness. May be invoked
992
+ * like below:
993
+ *
994
+ * does('foo', 'eql', 'foo')
995
+ * does([1,2], 'include', 1, 2)
996
+ *
997
+ * External hooks are not run for internal assertions
998
+ * performed by does().
999
+ *
1000
+ * @param {mixed} actual
1001
+ * @param {string} matcher
1002
+ * @param {...} expected
1003
+ * @return {mixed}
1004
+ * @api private
1005
+ */
1006
+
1007
+ does : function(actual, matcher, expected) {
1008
+ var assertion = new JSpec.Assertion(JSpec.matchers[matcher], actual, toArray(arguments, 2))
1009
+ return assertion.run().result
1010
+ },
1011
+
1012
+ /**
1013
+ * Perform an assertion.
1014
+ *
1015
+ * expect(true).to('be', true)
1016
+ * expect('foo').not_to('include', 'bar')
1017
+ * expect([1, [2]]).to('include', 1, [2])
1018
+ *
1019
+ * @param {mixed} actual
1020
+ * @return {hash}
1021
+ * @api public
1022
+ */
1023
+
1024
+ expect : function(actual) {
1025
+ function assert(matcher, args, negate) {
1026
+ var expected = toArray(args, 1)
1027
+ matcher.negate = negate
1028
+ assertion = new JSpec.Assertion(matcher, actual, expected, negate)
1029
+ hook('beforeAssertion', assertion)
1030
+ if (matcher.defer) assertion.run()
1031
+ else JSpec.currentSpec.assertions.push(assertion.run().report()), hook('afterAssertion', assertion)
1032
+ return assertion.result
1033
+ }
1034
+
1035
+ function to(matcher) {
1036
+ return assert(matcher, arguments, false)
1037
+ }
1038
+
1039
+ function not_to(matcher) {
1040
+ return assert(matcher, arguments, true)
1041
+ }
1042
+
1043
+ return {
1044
+ to : to,
1045
+ should : to,
1046
+ not_to: not_to,
1047
+ should_not : not_to
1048
+ }
1049
+ },
1050
+
1051
+ /**
1052
+ * Strim whitespace or chars.
1053
+ *
1054
+ * @param {string} string
1055
+ * @param {string} chars
1056
+ * @return {string}
1057
+ * @api public
1058
+ */
1059
+
1060
+ strip : function(string, chars) {
1061
+ return string.
1062
+ replace(new RegExp('[' + (chars || '\\s') + ']*$'), '').
1063
+ replace(new RegExp('^[' + (chars || '\\s') + ']*'), '')
1064
+ },
1065
+
1066
+ /**
1067
+ * Call an iterator callback with arguments a, or b
1068
+ * depending on the arity of the callback.
1069
+ *
1070
+ * @param {function} callback
1071
+ * @param {mixed} a
1072
+ * @param {mixed} b
1073
+ * @return {mixed}
1074
+ * @api private
1075
+ */
1076
+
1077
+ callIterator : function(callback, a, b) {
1078
+ return callback.length == 1 ? callback(b) : callback(a, b)
1079
+ },
1080
+
1081
+ /**
1082
+ * Extend an object with another.
1083
+ *
1084
+ * @param {object} object
1085
+ * @param {object} other
1086
+ * @api public
1087
+ */
1088
+
1089
+ extend : function(object, other) {
1090
+ each(other, function(property, value){
1091
+ object[property] = value
1092
+ })
1093
+ },
1094
+
1095
+ /**
1096
+ * Iterate an object, invoking the given callback.
1097
+ *
1098
+ * @param {hash, array} object
1099
+ * @param {function} callback
1100
+ * @return {JSpec}
1101
+ * @api public
1102
+ */
1103
+
1104
+ each : function(object, callback) {
1105
+ if (object.constructor == Array)
1106
+ for (var i = 0, len = object.length; i < len; ++i)
1107
+ callIterator(callback, i, object[i])
1108
+ else
1109
+ for (var key in object)
1110
+ if (object.hasOwnProperty(key))
1111
+ callIterator(callback, key, object[key])
1112
+ },
1113
+
1114
+ /**
1115
+ * Iterate with memo.
1116
+ *
1117
+ * @param {hash, array} object
1118
+ * @param {object} memo
1119
+ * @param {function} callback
1120
+ * @return {object}
1121
+ * @api public
1122
+ */
1123
+
1124
+ inject : function(object, memo, callback) {
1125
+ each(object, function(key, value){
1126
+ memo = (callback.length == 2 ?
1127
+ callback(memo, value):
1128
+ callback(memo, key, value)) ||
1129
+ memo
1130
+ })
1131
+ return memo
1132
+ },
1133
+
1134
+ /**
1135
+ * Destub _object_'s _method_. When no _method_ is passed
1136
+ * all stubbed methods are destubbed. When no arguments
1137
+ * are passed every object found in JSpec.stubbed will be
1138
+ * destubbed.
1139
+ *
1140
+ * @param {mixed} object
1141
+ * @param {string} method
1142
+ * @api public
1143
+ */
1144
+
1145
+ destub : function(object, method) {
1146
+ if (method) {
1147
+ if (object['__prototype__' + method])
1148
+ delete object[method]
1149
+ else
1150
+ object[method] = object['__original__' + method]
1151
+ delete object['__prototype__' + method]
1152
+ delete object['__original____' + method]
1153
+ }
1154
+ else if (object) {
1155
+ for (var key in object)
1156
+ if (captures = key.match(/^(?:__prototype__|__original__)(.*)/))
1157
+ destub(object, captures[1])
1158
+ }
1159
+ else
1160
+ while (JSpec.stubbed.length)
1161
+ destub(JSpec.stubbed.shift())
1162
+ },
1163
+
1164
+ /**
1165
+ * Stub _object_'s _method_.
1166
+ *
1167
+ * stub(foo, 'toString').and_return('bar')
1168
+ *
1169
+ * @param {mixed} object
1170
+ * @param {string} method
1171
+ * @return {hash}
1172
+ * @api public
1173
+ */
1174
+
1175
+ stub : function(object, method) {
1176
+ hook('stubbing', object, method)
1177
+ JSpec.stubbed.push(object)
1178
+ var type = object.hasOwnProperty(method) ? '__original__' : '__prototype__'
1179
+ object[type + method] = object[method]
1180
+ object[method] = function(){}
1181
+ return {
1182
+ and_return : function(value) {
1183
+ if (typeof value == 'function') object[method] = value
1184
+ else object[method] = function(){ return value }
1185
+ }
1186
+ }
1187
+ },
1188
+
1189
+ /**
1190
+ * Map callback return values.
1191
+ *
1192
+ * @param {hash, array} object
1193
+ * @param {function} callback
1194
+ * @return {array}
1195
+ * @api public
1196
+ */
1197
+
1198
+ map : function(object, callback) {
1199
+ return inject(object, [], function(memo, key, value){
1200
+ memo.push(callIterator(callback, key, value))
1201
+ })
1202
+ },
1203
+
1204
+ /**
1205
+ * Returns the first matching expression or null.
1206
+ *
1207
+ * @param {hash, array} object
1208
+ * @param {function} callback
1209
+ * @return {mixed}
1210
+ * @api public
1211
+ */
1212
+
1213
+ any : function(object, callback) {
1214
+ return inject(object, null, function(state, key, value){
1215
+ if (state == undefined)
1216
+ return callIterator(callback, key, value) ? value : state
1217
+ })
1218
+ },
1219
+
1220
+ /**
1221
+ * Returns an array of values collected when the callback
1222
+ * given evaluates to true.
1223
+ *
1224
+ * @param {hash, array} object
1225
+ * @return {function} callback
1226
+ * @return {array}
1227
+ * @api public
1228
+ */
1229
+
1230
+ select : function(object, callback) {
1231
+ return inject(object, [], function(selected, key, value){
1232
+ if (callIterator(callback, key, value))
1233
+ selected.push(value)
1234
+ })
1235
+ },
1236
+
1237
+ /**
1238
+ * Define matchers.
1239
+ *
1240
+ * @param {hash} matchers
1241
+ * @api public
1242
+ */
1243
+
1244
+ addMatchers : function(matchers) {
1245
+ each(matchers, function(name, body){
1246
+ JSpec.addMatcher(name, body)
1247
+ })
1248
+ },
1249
+
1250
+ /**
1251
+ * Define a matcher.
1252
+ *
1253
+ * @param {string} name
1254
+ * @param {hash, function, string} body
1255
+ * @api public
1256
+ */
1257
+
1258
+ addMatcher : function(name, body) {
1259
+ hook('addingMatcher', name, body)
1260
+ if (name.indexOf(' ') != -1) {
1261
+ var matchers = name.split(/\s+/)
1262
+ var prefix = matchers.shift()
1263
+ each(matchers, function(name) {
1264
+ JSpec.addMatcher(prefix + '_' + name, body(name))
1265
+ })
1266
+ }
1267
+ this.matchers[name] = this.normalizeMatcherMessage(this.normalizeMatcherBody(body))
1268
+ this.matchers[name].name = name
1269
+ },
1270
+
1271
+ /**
1272
+ * Add a root suite to JSpec.
1273
+ *
1274
+ * @param {string} description
1275
+ * @param {body} function
1276
+ * @api public
1277
+ */
1278
+
1279
+ describe : function(description, body) {
1280
+ var suite = new JSpec.Suite(description, body)
1281
+ hook('addingSuite', suite)
1282
+ this.allSuites.push(suite)
1283
+ this.suites.push(suite)
1284
+ },
1285
+
1286
+ /**
1287
+ * Return the contents of a function body.
1288
+ *
1289
+ * @param {function} body
1290
+ * @return {string}
1291
+ * @api public
1292
+ */
1293
+
1294
+ contentsOf : function(body) {
1295
+ return body.toString().match(/^[^\{]*{((.*\n*)*)}/m)[1]
1296
+ },
1297
+
1298
+ /**
1299
+ * Evaluate a JSpec capture body.
1300
+ *
1301
+ * @param {function} body
1302
+ * @param {string} errorMessage (optional)
1303
+ * @return {Type}
1304
+ * @api private
1305
+ */
1306
+
1307
+ evalBody : function(body, errorMessage) {
1308
+ var dsl = this.DSL || this.DSLs.snake
1309
+ var matchers = this.matchers
1310
+ var context = this.context || this.defaultContext
1311
+ var contents = this.contentsOf(body)
1312
+ hook('evaluatingBody', dsl, matchers, context, contents)
1313
+ try { with (dsl){ with (context) { with (matchers) { eval(contents) }}} }
1314
+ catch(e) { error(errorMessage, e) }
1315
+ },
1316
+
1317
+ /**
1318
+ * Pre-process a string of JSpec.
1319
+ *
1320
+ * @param {string} input
1321
+ * @return {string}
1322
+ * @api private
1323
+ */
1324
+
1325
+ preprocess : function(input) {
1326
+ if (typeof input != 'string') return
1327
+ input = hookImmutable('preprocessing', input)
1328
+ return input.
1329
+ replace(/\t/g, ' ').
1330
+ replace(/\r\n|\n|\r/g, '\n').
1331
+ split('__END__')[0].
1332
+ replace(/([\w\.]+)\.(stub|destub)\((.*?)\)$/gm, '$2($1, $3)').
1333
+ replace(/describe\s+(.*?)$/gm, 'describe($1, function(){').
1334
+ replace(/^\s+it\s+(.*?)$/gm, ' it($1, function(){').
1335
+ replace(/^ *(before_each|after_each|before|after)(?= |\n|$)/gm, 'JSpec.currentSuite.addHook("$1", function(){').
1336
+ replace(/^\s*end(?=\s|$)/gm, '});').
1337
+ replace(/-\{/g, 'function(){').
1338
+ replace(/(\d+)\.\.(\d+)/g, function(_, a, b){ return range(a, b) }).
1339
+ replace(/\.should([_\.]not)?[_\.](\w+)(?: |;|$)(.*)$/gm, '.should$1_$2($3)').
1340
+ replace(/([\/\s]*)(.+?)\.(should(?:[_\.]not)?)[_\.](\w+)\((.*)\)\s*;?$/gm, '$1 expect($2).$3($4, $5)').
1341
+ replace(/, \)/g, ')').
1342
+ replace(/should\.not/g, 'should_not')
1343
+ },
1344
+
1345
+ /**
1346
+ * Create a range string which can be evaluated to a native array.
1347
+ *
1348
+ * @param {int} start
1349
+ * @param {int} end
1350
+ * @return {string}
1351
+ * @api public
1352
+ */
1353
+
1354
+ range : function(start, end) {
1355
+ var current = parseInt(start), end = parseInt(end), values = [current]
1356
+ if (end > current) while (++current <= end) values.push(current)
1357
+ else while (--current >= end) values.push(current)
1358
+ return '[' + values + ']'
1359
+ },
1360
+
1361
+ /**
1362
+ * Report on the results.
1363
+ *
1364
+ * @api public
1365
+ */
1366
+
1367
+ report : function() {
1368
+ this.duration = Number(new Date) - this.start
1369
+ hook('reporting', JSpec.options)
1370
+ new (JSpec.options.reporter || JSpec.reporters.DOM)(JSpec, JSpec.options)
1371
+ },
1372
+
1373
+ /**
1374
+ * Run the spec suites. Options are merged
1375
+ * with JSpec options when present.
1376
+ *
1377
+ * @param {hash} options
1378
+ * @return {JSpec}
1379
+ * @api public
1380
+ */
1381
+
1382
+ run : function(options) {
1383
+ if (any(hook('running'), haveStopped)) return this
1384
+ if (options) extend(this.options, options)
1385
+ this.start = Number(new Date)
1386
+ each(this.suites, function(suite) { JSpec.runSuite(suite) })
1387
+ return this
1388
+ },
1389
+
1390
+ /**
1391
+ * Run a suite.
1392
+ *
1393
+ * @param {Suite} suite
1394
+ * @api public
1395
+ */
1396
+
1397
+ runSuite : function(suite) {
1398
+ this.currentSuite = suite
1399
+ this.evalBody(suite.body)
1400
+ suite.ran = true
1401
+ hook('beforeSuite', suite), suite.hook('before')
1402
+ each(suite.specs, function(spec) {
1403
+ hook('beforeSpec', spec)
1404
+ suite.hook('before_each')
1405
+ JSpec.runSpec(spec)
1406
+ hook('afterSpec', spec)
1407
+ suite.hook('after_each')
1408
+ })
1409
+ if (suite.hasSuites()) {
1410
+ each(suite.suites, function(suite) {
1411
+ JSpec.runSuite(suite)
1412
+ })
1413
+ }
1414
+ hook('afterSuite', suite), suite.hook('after')
1415
+ this.stats.suitesFinished++
1416
+ },
1417
+
1418
+ /**
1419
+ * Report a failure for the current spec.
1420
+ *
1421
+ * @param {string} message
1422
+ * @api public
1423
+ */
1424
+
1425
+ fail : function(message) {
1426
+ JSpec.currentSpec.fail(message)
1427
+ },
1428
+
1429
+ /**
1430
+ * Report a passing assertion for the current spec.
1431
+ *
1432
+ * @param {string} message
1433
+ * @api public
1434
+ */
1435
+
1436
+ pass : function(message) {
1437
+ JSpec.currentSpec.pass(message)
1438
+ },
1439
+
1440
+ /**
1441
+ * Run a spec.
1442
+ *
1443
+ * @param {Spec} spec
1444
+ * @api public
1445
+ */
1446
+
1447
+ runSpec : function(spec) {
1448
+ this.currentSpec = spec
1449
+ try { this.evalBody(spec.body) }
1450
+ catch (e) { fail(e) }
1451
+ spec.runDeferredAssertions()
1452
+ destub()
1453
+ this.stats.specsFinished++
1454
+ this.stats.assertions += spec.assertions.length
1455
+ },
1456
+
1457
+ /**
1458
+ * Require a dependency, with optional message.
1459
+ *
1460
+ * @param {string} dependency
1461
+ * @param {string} message (optional)
1462
+ * @return {JSpec}
1463
+ * @api public
1464
+ */
1465
+
1466
+ requires : function(dependency, message) {
1467
+ hook('requiring', dependency, message)
1468
+ try { eval(dependency) }
1469
+ catch (e) { throw 'JSpec depends on ' + dependency + ' ' + message }
1470
+ return this
1471
+ },
1472
+
1473
+ /**
1474
+ * Query against the current query strings keys
1475
+ * or the queryString specified.
1476
+ *
1477
+ * @param {string} key
1478
+ * @param {string} queryString
1479
+ * @return {string, null}
1480
+ * @api private
1481
+ */
1482
+
1483
+ query : function(key, queryString) {
1484
+ var queryString = (queryString || (main.location ? main.location.search : null) || '').substring(1)
1485
+ return inject(queryString.split('&'), null, function(value, pair){
1486
+ parts = pair.split('=')
1487
+ return parts[0] == key ? parts[1].replace(/%20|\+/gmi, ' ') : value
1488
+ })
1489
+ },
1490
+
1491
+ /**
1492
+ * Throw a JSpec related error.
1493
+ *
1494
+ * @param {string} message
1495
+ * @param {Exception} e
1496
+ * @api public
1497
+ */
1498
+
1499
+ error : function(message, e) {
1500
+ throw (message ? message : '') + e.toString() +
1501
+ (e.line ? ' near line ' + e.line : '')
1502
+ },
1503
+
1504
+ /**
1505
+ * Ad-hoc POST request for JSpec server usage.
1506
+ *
1507
+ * @param {string} uri
1508
+ * @param {string} data
1509
+ * @api private
1510
+ */
1511
+
1512
+ post : function(uri, data) {
1513
+ if (any(hook('posting', uri, data), haveStopped)) return
1514
+ var request = this.xhr()
1515
+ request.open('POST', uri, false)
1516
+ request.setRequestHeader('Content-Type', 'application/json')
1517
+ request.send(JSpec.JSON.encode(data))
1518
+ },
1519
+
1520
+ /**
1521
+ * Instantiate an XMLHttpRequest.
1522
+ *
1523
+ * Here we utilize IE's lame ActiveXObjects first which
1524
+ * allow IE access serve files via the file: protocol, otherwise
1525
+ * we then default to XMLHttpRequest.
1526
+ *
1527
+ * @return {XMLHttpRequest, ActiveXObject}
1528
+ * @api private
1529
+ */
1530
+
1531
+ xhr : function() {
1532
+ return this.ieXhr() || new JSpec.request
1533
+ },
1534
+
1535
+ /**
1536
+ * Return Microsoft piece of crap ActiveXObject.
1537
+ *
1538
+ * @return {ActiveXObject}
1539
+ * @api public
1540
+ */
1541
+
1542
+ ieXhr : function() {
1543
+ function object(str) {
1544
+ try { return new ActiveXObject(str) } catch(e) {}
1545
+ }
1546
+ return object('Msxml2.XMLHTTP.6.0') ||
1547
+ object('Msxml2.XMLHTTP.3.0') ||
1548
+ object('Msxml2.XMLHTTP') ||
1549
+ object('Microsoft.XMLHTTP')
1550
+ },
1551
+
1552
+ /**
1553
+ * Check for HTTP request support.
1554
+ *
1555
+ * @return {bool}
1556
+ * @api private
1557
+ */
1558
+
1559
+ hasXhr : function() {
1560
+ return JSpec.request || 'ActiveXObject' in main
1561
+ },
1562
+
1563
+ /**
1564
+ * Try loading _file_ returning the contents
1565
+ * string or null. Chain to locate / read a file.
1566
+ *
1567
+ * @param {string} file
1568
+ * @return {string}
1569
+ * @api public
1570
+ */
1571
+
1572
+ tryLoading : function(file) {
1573
+ try { return JSpec.load(file) } catch (e) {}
1574
+ },
1575
+
1576
+ /**
1577
+ * Load a _file_'s contents.
1578
+ *
1579
+ * @param {string} file
1580
+ * @param {function} callback
1581
+ * @return {string}
1582
+ * @api public
1583
+ */
1584
+
1585
+ load : function(file, callback) {
1586
+ if (any(hook('loading', file), haveStopped)) return
1587
+ if ('readFile' in main)
1588
+ return readFile(file)
1589
+ else if (this.hasXhr()) {
1590
+ var request = this.xhr()
1591
+ request.open('GET', file, false)
1592
+ request.send(null)
1593
+ if (request.readyState == 4 &&
1594
+ (request.status == 0 ||
1595
+ request.status.toString().charAt(0) == 2))
1596
+ return request.responseText
1597
+ }
1598
+ else
1599
+ error("failed to load `" + file + "'")
1600
+ },
1601
+
1602
+ /**
1603
+ * Load, pre-process, and evaluate a file.
1604
+ *
1605
+ * @param {string} file
1606
+ * @param {JSpec}
1607
+ * @api public
1608
+ */
1609
+
1610
+ exec : function(file) {
1611
+ if (any(hook('executing', file), haveStopped)) return this
1612
+ eval('with (JSpec){' + this.preprocess(this.load(file)) + '}')
1613
+ return this
1614
+ }
1615
+ }
1616
+
1617
+ // --- Node.js support
1618
+
1619
+ if (typeof GLOBAL === 'object' && typeof exports === 'object')
1620
+ quit = process.exit,
1621
+ print = require('sys').puts,
1622
+ readFile = require('fs').readFileSync
1623
+
1624
+ // --- Utility functions
1625
+
1626
+ var main = this,
1627
+ find = JSpec.any,
1628
+ utils = 'haveStopped stub hookImmutable hook destub map any last pass fail range each option inject select \
1629
+ error escape extend puts query strip color does addMatchers callIterator toArray equal'.split(/\s+/)
1630
+ while (utils.length) eval('var ' + utils[0] + ' = JSpec.' + utils.shift())
1631
+ if (!main.setTimeout) main.setTimeout = function(callback){ callback() }
1632
+
1633
+ // --- Matchers
1634
+
1635
+ addMatchers({
1636
+ equal : "===",
1637
+ eql : "equal(actual, expected)",
1638
+ be : "alias equal",
1639
+ be_greater_than : ">",
1640
+ be_less_than : "<",
1641
+ be_at_least : ">=",
1642
+ be_at_most : "<=",
1643
+ be_a : "actual.constructor == expected",
1644
+ be_an : "alias be_a",
1645
+ be_an_instance_of : "actual instanceof expected",
1646
+ be_null : "actual == null",
1647
+ be_true : "actual == true",
1648
+ be_false : "actual == false",
1649
+ be_undefined : "typeof actual == 'undefined'",
1650
+ be_type : "typeof actual == expected",
1651
+ match : "typeof actual == 'string' ? actual.match(expected) : false",
1652
+ respond_to : "typeof actual[expected] == 'function'",
1653
+ have_length : "actual.length == expected",
1654
+ be_within : "actual >= expected[0] && actual <= last(expected)",
1655
+ have_length_within : "actual.length >= expected[0] && actual.length <= last(expected)",
1656
+
1657
+ receive : { defer : true, match : function(actual, method, times) {
1658
+ proxy = new JSpec.ProxyAssertion(actual, method, times, this.negate)
1659
+ JSpec.currentSpec.assertions.push(proxy)
1660
+ return proxy
1661
+ }},
1662
+
1663
+ be_empty : function(actual) {
1664
+ if (actual.constructor == Object && actual.length == undefined)
1665
+ for (var key in actual)
1666
+ return false;
1667
+ return !actual.length
1668
+ },
1669
+
1670
+ include : function(actual) {
1671
+ for (state = true, i = 1; i < arguments.length; i++) {
1672
+ arg = arguments[i]
1673
+ switch (actual.constructor) {
1674
+ case String:
1675
+ case Number:
1676
+ case RegExp:
1677
+ case Function:
1678
+ state = actual.toString().indexOf(arg) !== -1
1679
+ break
1680
+
1681
+ case Object:
1682
+ state = arg in actual
1683
+ break
1684
+
1685
+ case Array:
1686
+ state = any(actual, function(value){ return equal(value, arg) })
1687
+ break
1688
+ }
1689
+ if (!state) return false
1690
+ }
1691
+ return true
1692
+ },
1693
+
1694
+ throw_error : { match : function(actual, expected, message) {
1695
+ try { actual() }
1696
+ catch (e) {
1697
+ this.e = e
1698
+ var assert = function(arg) {
1699
+ switch (arg.constructor) {
1700
+ case RegExp : return arg.test(e.message || e.toString())
1701
+ case String : return arg == (e.message || e.toString())
1702
+ case Function : return e instanceof arg || e.name == arg.name
1703
+ }
1704
+ }
1705
+ return message ? assert(expected) && assert(message) :
1706
+ expected ? assert(expected) :
1707
+ true
1708
+ }
1709
+ }, message : function(actual, expected, negate) {
1710
+ // TODO: refactor when actual is not in expected [0]
1711
+ var message_for = function(i) {
1712
+ if (expected[i] == undefined) return 'exception'
1713
+ switch (expected[i].constructor) {
1714
+ case RegExp : return 'exception matching ' + puts(expected[i])
1715
+ case String : return 'exception of ' + puts(expected[i])
1716
+ case Function : return expected[i].name || 'Error'
1717
+ }
1718
+ }
1719
+ exception = message_for(1) + (expected[2] ? ' and ' + message_for(2) : '')
1720
+ return 'expected ' + exception + (negate ? ' not ' : '' ) +
1721
+ ' to be thrown, but ' + (this.e ? 'got ' + puts(this.e) : 'nothing was')
1722
+ }},
1723
+
1724
+ have : function(actual, length, property) {
1725
+ return actual[property].length == length
1726
+ },
1727
+
1728
+ have_at_least : function(actual, length, property) {
1729
+ return actual[property].length >= length
1730
+ },
1731
+
1732
+ have_at_most :function(actual, length, property) {
1733
+ return actual[property].length <= length
1734
+ },
1735
+
1736
+ have_within : function(actual, range, property) {
1737
+ length = actual[property].length
1738
+ return length >= range.shift() && length <= range.pop()
1739
+ },
1740
+
1741
+ have_prop : function(actual, property, value) {
1742
+ return actual[property] == null ||
1743
+ actual[property] instanceof Function ? false:
1744
+ value == null ? true:
1745
+ does(actual[property], 'eql', value)
1746
+ },
1747
+
1748
+ have_property : function(actual, property, value) {
1749
+ return actual[property] == null ||
1750
+ actual[property] instanceof Function ? false:
1751
+ value == null ? true:
1752
+ value === actual[property]
1753
+ }
1754
+ })
1755
+
1756
+ })()