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