bootcamp 0.1.0

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 (126) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/.rvmrc +1 -0
  4. data/Gemfile +14 -0
  5. data/Gemfile.lock +36 -0
  6. data/LICENSE.txt +20 -0
  7. data/NOTES +3 -0
  8. data/README.rdoc +49 -0
  9. data/Rakefile +42 -0
  10. data/VERSION +1 -0
  11. data/bin/bootcamp +8 -0
  12. data/bootcamp.gemspec +190 -0
  13. data/lib/bootcamp.rb +22 -0
  14. data/lib/bootcamp/armory.rb +20 -0
  15. data/lib/bootcamp/drill_instructor.rb +95 -0
  16. data/lib/bootcamp/formations/core.rb +13 -0
  17. data/lib/bootcamp/formations/html.rb +17 -0
  18. data/lib/bootcamp/formations/jquery.rb +13 -0
  19. data/lib/bootcamp/recruit.rb +28 -0
  20. data/spec/bootcamp_spec.rb +12 -0
  21. data/spec/spec_helper.rb +12 -0
  22. data/vendor/core.js +1 -0
  23. data/vendor/dojo.js +14 -0
  24. data/vendor/jquery.js +18 -0
  25. data/vendor/midori.js +1502 -0
  26. data/vendor/mootools.js +486 -0
  27. data/vendor/prototype.js +6082 -0
  28. data/vendor/test_suites/jasmine/SpecRunner.html +27 -0
  29. data/vendor/test_suites/jasmine/lib/jasmine-1.0.2/MIT.LICENSE +20 -0
  30. data/vendor/test_suites/jasmine/lib/jasmine-1.0.2/jasmine-html.js +188 -0
  31. data/vendor/test_suites/jasmine/lib/jasmine-1.0.2/jasmine.css +166 -0
  32. data/vendor/test_suites/jasmine/lib/jasmine-1.0.2/jasmine.js +2421 -0
  33. data/vendor/test_suites/jasmine/spec/PlayerSpec.js +58 -0
  34. data/vendor/test_suites/jasmine/spec/SpecHelper.js +9 -0
  35. data/vendor/test_suites/jasmine/src/Player.js +22 -0
  36. data/vendor/test_suites/jasmine/src/Song.js +7 -0
  37. data/vendor/test_suites/jspec/History.md +790 -0
  38. data/vendor/test_suites/jspec/Manifest +79 -0
  39. data/vendor/test_suites/jspec/README.md +1030 -0
  40. data/vendor/test_suites/jspec/Rakefile +28 -0
  41. data/vendor/test_suites/jspec/bin/jspec +182 -0
  42. data/vendor/test_suites/jspec/jspec.gemspec +44 -0
  43. data/vendor/test_suites/jspec/lib/images/bg.png +0 -0
  44. data/vendor/test_suites/jspec/lib/images/hr.png +0 -0
  45. data/vendor/test_suites/jspec/lib/images/loading.gif +0 -0
  46. data/vendor/test_suites/jspec/lib/images/sprites.bg.png +0 -0
  47. data/vendor/test_suites/jspec/lib/images/sprites.png +0 -0
  48. data/vendor/test_suites/jspec/lib/images/vr.png +0 -0
  49. data/vendor/test_suites/jspec/lib/jspec.css +149 -0
  50. data/vendor/test_suites/jspec/lib/jspec.growl.js +115 -0
  51. data/vendor/test_suites/jspec/lib/jspec.jquery.js +72 -0
  52. data/vendor/test_suites/jspec/lib/jspec.js +1876 -0
  53. data/vendor/test_suites/jspec/lib/jspec.shell.js +39 -0
  54. data/vendor/test_suites/jspec/lib/jspec.timers.js +90 -0
  55. data/vendor/test_suites/jspec/lib/jspec.xhr.js +195 -0
  56. data/vendor/test_suites/jspec/spec/commands/example_command.rb +19 -0
  57. data/vendor/test_suites/jspec/spec/dom.html +33 -0
  58. data/vendor/test_suites/jspec/spec/fixtures/test.html +1 -0
  59. data/vendor/test_suites/jspec/spec/fixtures/test.json +1 -0
  60. data/vendor/test_suites/jspec/spec/fixtures/test.xml +5 -0
  61. data/vendor/test_suites/jspec/spec/node.js +17 -0
  62. data/vendor/test_suites/jspec/spec/rhino.js +23 -0
  63. data/vendor/test_suites/jspec/spec/ruby/bin/init_spec.rb +101 -0
  64. data/vendor/test_suites/jspec/spec/ruby/bin/install_spec.rb +141 -0
  65. data/vendor/test_suites/jspec/spec/ruby/bin/run_spec.rb +0 -0
  66. data/vendor/test_suites/jspec/spec/ruby/bin/shell_spec.rb +13 -0
  67. data/vendor/test_suites/jspec/spec/ruby/bin/spec_helper.rb +8 -0
  68. data/vendor/test_suites/jspec/spec/ruby/bin/update_spec.rb +72 -0
  69. data/vendor/test_suites/jspec/spec/server.html +29 -0
  70. data/vendor/test_suites/jspec/spec/server.rb +2 -0
  71. data/vendor/test_suites/jspec/spec/support/env.js +10118 -0
  72. data/vendor/test_suites/jspec/spec/support/jquery.js +4376 -0
  73. data/vendor/test_suites/jspec/spec/unit/helpers.js +64 -0
  74. data/vendor/test_suites/jspec/spec/unit/spec.fixtures.js +24 -0
  75. data/vendor/test_suites/jspec/spec/unit/spec.grammar-less.js +34 -0
  76. data/vendor/test_suites/jspec/spec/unit/spec.grammar.js +275 -0
  77. data/vendor/test_suites/jspec/spec/unit/spec.jquery.js +178 -0
  78. data/vendor/test_suites/jspec/spec/unit/spec.jquery.xhr.js +84 -0
  79. data/vendor/test_suites/jspec/spec/unit/spec.js +187 -0
  80. data/vendor/test_suites/jspec/spec/unit/spec.matchers.js +577 -0
  81. data/vendor/test_suites/jspec/spec/unit/spec.modules.js +51 -0
  82. data/vendor/test_suites/jspec/spec/unit/spec.shared-behaviors.js +279 -0
  83. data/vendor/test_suites/jspec/spec/unit/spec.utils.js +346 -0
  84. data/vendor/test_suites/jspec/spec/unit/spec.xhr.js +157 -0
  85. data/vendor/test_suites/jspec/src/browsers.rb +279 -0
  86. data/vendor/test_suites/jspec/src/helpers.rb +67 -0
  87. data/vendor/test_suites/jspec/src/installables.rb +229 -0
  88. data/vendor/test_suites/jspec/src/project.rb +341 -0
  89. data/vendor/test_suites/jspec/src/routes.rb +57 -0
  90. data/vendor/test_suites/jspec/src/server.rb +88 -0
  91. data/vendor/test_suites/jspec/support/js.jar +0 -0
  92. data/vendor/test_suites/jspec/templates/default/History.md +5 -0
  93. data/vendor/test_suites/jspec/templates/default/Readme.md +29 -0
  94. data/vendor/test_suites/jspec/templates/default/lib/yourlib.js +2 -0
  95. data/vendor/test_suites/jspec/templates/default/spec/commands/example_command.rb +19 -0
  96. data/vendor/test_suites/jspec/templates/default/spec/dom.html +22 -0
  97. data/vendor/test_suites/jspec/templates/default/spec/node.js +10 -0
  98. data/vendor/test_suites/jspec/templates/default/spec/rhino.js +10 -0
  99. data/vendor/test_suites/jspec/templates/default/spec/server.html +18 -0
  100. data/vendor/test_suites/jspec/templates/default/spec/server.rb +4 -0
  101. data/vendor/test_suites/jspec/templates/default/spec/unit/spec.helper.js +0 -0
  102. data/vendor/test_suites/jspec/templates/default/spec/unit/spec.js +8 -0
  103. data/vendor/test_suites/jspec/templates/node/History.md +5 -0
  104. data/vendor/test_suites/jspec/templates/node/Readme.md +29 -0
  105. data/vendor/test_suites/jspec/templates/node/lib/yourlib.js +2 -0
  106. data/vendor/test_suites/jspec/templates/node/spec/node.js +10 -0
  107. data/vendor/test_suites/jspec/templates/node/spec/unit/spec.helper.js +0 -0
  108. data/vendor/test_suites/jspec/templates/node/spec/unit/spec.js +8 -0
  109. data/vendor/test_suites/jspec/templates/rails/commands/example_commands.rb +19 -0
  110. data/vendor/test_suites/jspec/templates/rails/dom.html +22 -0
  111. data/vendor/test_suites/jspec/templates/rails/rhino.js +10 -0
  112. data/vendor/test_suites/jspec/templates/rails/server.html +18 -0
  113. data/vendor/test_suites/jspec/templates/rails/server.rb +4 -0
  114. data/vendor/test_suites/jspec/templates/rails/unit/spec.helper.js +0 -0
  115. data/vendor/test_suites/jspec/templates/rails/unit/spec.js +8 -0
  116. data/vendor/test_suites/qunit/README.md +24 -0
  117. data/vendor/test_suites/qunit/package.json +21 -0
  118. data/vendor/test_suites/qunit/qunit/qunit.css +225 -0
  119. data/vendor/test_suites/qunit/qunit/qunit.js +1448 -0
  120. data/vendor/test_suites/qunit/test/headless.html +24 -0
  121. data/vendor/test_suites/qunit/test/index.html +19 -0
  122. data/vendor/test_suites/qunit/test/logs.html +17 -0
  123. data/vendor/test_suites/qunit/test/logs.js +150 -0
  124. data/vendor/test_suites/qunit/test/same.js +1421 -0
  125. data/vendor/test_suites/qunit/test/test.js +324 -0
  126. metadata +282 -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,1876 @@
1
+
2
+ // JSpec - Core - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
3
+
4
+ ;(function(){
5
+
6
+ JSpec = {
7
+ version : '4.0.0',
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
+ var old = object[method]
265
+
266
+ // Proxy
267
+
268
+ object[method] = function(){
269
+ 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
+ return "\u001B[" + {
898
+ bold : 1,
899
+ black : 30,
900
+ red : 31,
901
+ green : 32,
902
+ yellow : 33,
903
+ blue : 34,
904
+ magenta : 35,
905
+ cyan : 36,
906
+ white : 37
907
+ }[color] + 'm' + string + "\u001B[0m"
908
+ },
909
+
910
+ /**
911
+ * Default matcher message callback.
912
+ *
913
+ * @api private
914
+ */
915
+
916
+ defaultMatcherMessage : function(actual, expected, negate, name) {
917
+ return 'expected ' + puts(actual) + ' to ' +
918
+ (negate ? 'not ' : '') +
919
+ name.replace(/_/g, ' ') +
920
+ ' ' + (expected.length > 1 ?
921
+ puts.apply(this, expected.slice(1)) :
922
+ '')
923
+ },
924
+
925
+ /**
926
+ * Normalize a matcher message.
927
+ *
928
+ * When no messge callback is present the defaultMatcherMessage
929
+ * will be assigned, will suffice for most matchers.
930
+ *
931
+ * @param {hash} matcher
932
+ * @return {hash}
933
+ * @api public
934
+ */
935
+
936
+ normalizeMatcherMessage : function(matcher) {
937
+ if (typeof matcher.message != 'function')
938
+ matcher.message = this.defaultMatcherMessage
939
+ return matcher
940
+ },
941
+
942
+ /**
943
+ * Normalize a matcher body
944
+ *
945
+ * This process allows the following conversions until
946
+ * the matcher is in its final normalized hash state.
947
+ *
948
+ * - '==' becomes 'actual == expected'
949
+ * - 'actual == expected' becomes 'return actual == expected'
950
+ * - function(actual, expected) { return actual == expected } becomes
951
+ * { match : function(actual, expected) { return actual == expected }}
952
+ *
953
+ * @param {mixed} body
954
+ * @return {hash}
955
+ * @api public
956
+ */
957
+
958
+ normalizeMatcherBody : function(body) {
959
+ switch (body.constructor) {
960
+ case String:
961
+ if (captures = body.match(/^alias (\w+)/)) return JSpec.matchers[last(captures)]
962
+ if (body.length < 4) body = 'actual ' + body + ' expected'
963
+ return { match: function(actual, expected) { return eval(body) }}
964
+
965
+ case Function:
966
+ return { match: body }
967
+
968
+ default:
969
+ return body
970
+ }
971
+ },
972
+
973
+ /**
974
+ * Get option value. This method first checks if
975
+ * the option key has been set via the query string,
976
+ * otherwise returning the options hash value.
977
+ *
978
+ * @param {string} key
979
+ * @return {mixed}
980
+ * @api public
981
+ */
982
+
983
+ option : function(key) {
984
+ return (value = query(key)) !== null ? value :
985
+ JSpec.options[key] || null
986
+ },
987
+
988
+ /**
989
+ * Check if object _a_, is equal to object _b_.
990
+ *
991
+ * @param {object} a
992
+ * @param {object} b
993
+ * @return {bool}
994
+ * @api private
995
+ */
996
+
997
+ equal: function(a, b) {
998
+ if (typeof a != typeof b) return
999
+ if (a === b) return true
1000
+ if (a instanceof RegExp)
1001
+ return a.toString() === b.toString()
1002
+ if (a instanceof Date)
1003
+ return Number(a) === Number(b)
1004
+ if (typeof a != 'object') return
1005
+ if (a.length !== undefined)
1006
+ if (a.length !== b.length) return
1007
+ else
1008
+ for (var i = 0, len = a.length; i < len; ++i)
1009
+ if (!equal(a[i], b[i]))
1010
+ return
1011
+ for (var key in a)
1012
+ if (!equal(a[key], b[key]))
1013
+ return
1014
+ return true
1015
+ },
1016
+
1017
+ /**
1018
+ * Return last element of an array.
1019
+ *
1020
+ * @param {array} array
1021
+ * @return {object}
1022
+ * @api public
1023
+ */
1024
+
1025
+ last : function(array) {
1026
+ return array[array.length - 1]
1027
+ },
1028
+
1029
+ /**
1030
+ * Convert object(s) to a print-friend string.
1031
+ *
1032
+ * @param {...} object
1033
+ * @return {string}
1034
+ * @api public
1035
+ */
1036
+
1037
+ puts : function(object) {
1038
+ if (arguments.length > 1)
1039
+ return map(toArray(arguments), function(arg){
1040
+ return puts(arg)
1041
+ }).join(', ')
1042
+ if (object === undefined) return 'undefined'
1043
+ if (object === null) return 'null'
1044
+ if (object === true) return 'true'
1045
+ if (object === false) return 'false'
1046
+ if (object.an_instance_of) return 'an instance of ' + object.an_instance_of.name
1047
+ if (object.jquery && object.selector.length > 0) return 'selector ' + puts(object.selector)
1048
+ if (object.jquery) return object.get(0).outerHTML
1049
+ if (object.nodeName) return object.outerHTML
1050
+ switch (object.constructor) {
1051
+ case Function: return object.name || object
1052
+ case String:
1053
+ return '"' + object
1054
+ .replace(/"/g, '\\"')
1055
+ .replace(/\n/g, '\\n')
1056
+ .replace(/\t/g, '\\t')
1057
+ + '"'
1058
+ case Array:
1059
+ return inject(object, '[', function(b, v){
1060
+ return b + ', ' + puts(v)
1061
+ }).replace('[,', '[') + ' ]'
1062
+ case Object:
1063
+ object.__hit__ = true
1064
+ return inject(object, '{', function(b, k, v) {
1065
+ if (k == '__hit__') return b
1066
+ return b + ', ' + k + ': ' + (v && v.__hit__ ? '<circular reference>' : puts(v))
1067
+ }).replace('{,', '{') + ' }'
1068
+ default:
1069
+ return object.toString()
1070
+ }
1071
+ },
1072
+
1073
+ /**
1074
+ * Escape HTML.
1075
+ *
1076
+ * @param {string} html
1077
+ * @return {string}
1078
+ * @api public
1079
+ */
1080
+
1081
+ escape : function(html) {
1082
+ return html.toString()
1083
+ .replace(/&/gmi, '&amp;')
1084
+ .replace(/"/gmi, '&quot;')
1085
+ .replace(/>/gmi, '&gt;')
1086
+ .replace(/</gmi, '&lt;')
1087
+ },
1088
+
1089
+ /**
1090
+ * Perform an assertion without reporting.
1091
+ *
1092
+ * This method is primarily used for internal
1093
+ * matchers in order retain DRYness. May be invoked
1094
+ * like below:
1095
+ *
1096
+ * does('foo', 'eql', 'foo')
1097
+ * does([1,2], 'include', 1, 2)
1098
+ *
1099
+ * External hooks are not run for internal assertions
1100
+ * performed by does().
1101
+ *
1102
+ * @param {mixed} actual
1103
+ * @param {string} matcher
1104
+ * @param {...} expected
1105
+ * @return {mixed}
1106
+ * @api private
1107
+ */
1108
+
1109
+ does : function(actual, matcher, expected) {
1110
+ var assertion = new JSpec.Assertion(JSpec.matchers[matcher], actual, toArray(arguments, 2))
1111
+ return assertion.run().result
1112
+ },
1113
+
1114
+ /**
1115
+ * Perform an assertion.
1116
+ *
1117
+ * expect(true).to('be', true)
1118
+ * expect('foo').not_to('include', 'bar')
1119
+ * expect([1, [2]]).to('include', 1, [2])
1120
+ *
1121
+ * @param {mixed} actual
1122
+ * @return {hash}
1123
+ * @api public
1124
+ */
1125
+
1126
+ expect : function(actual) {
1127
+ function assert(matcher, args, negate) {
1128
+ var expected = toArray(args, 1)
1129
+ matcher.negate = negate
1130
+ assertion = new JSpec.Assertion(matcher, actual, expected, negate)
1131
+ hook('beforeAssertion', assertion)
1132
+ if (matcher.defer) assertion.run()
1133
+ else JSpec.currentSpec.assertions.push(assertion.run().report()), hook('afterAssertion', assertion)
1134
+ return assertion.result
1135
+ }
1136
+
1137
+ function to(matcher) {
1138
+ return assert(matcher, arguments, false)
1139
+ }
1140
+
1141
+ function not_to(matcher) {
1142
+ return assert(matcher, arguments, true)
1143
+ }
1144
+
1145
+ return {
1146
+ to : to,
1147
+ should : to,
1148
+ not_to: not_to,
1149
+ should_not : not_to
1150
+ }
1151
+ },
1152
+
1153
+ /**
1154
+ * Strim whitespace or chars.
1155
+ *
1156
+ * @param {string} string
1157
+ * @param {string} chars
1158
+ * @return {string}
1159
+ * @api public
1160
+ */
1161
+
1162
+ strip : function(string, chars) {
1163
+ return string.
1164
+ replace(new RegExp('[' + (chars || '\\s') + ']*$'), '').
1165
+ replace(new RegExp('^[' + (chars || '\\s') + ']*'), '')
1166
+ },
1167
+
1168
+ /**
1169
+ * Call an iterator callback with arguments a, or b
1170
+ * depending on the arity of the callback.
1171
+ *
1172
+ * @param {function} callback
1173
+ * @param {mixed} a
1174
+ * @param {mixed} b
1175
+ * @return {mixed}
1176
+ * @api private
1177
+ */
1178
+
1179
+ callIterator : function(callback, a, b) {
1180
+ return callback.length == 1 ? callback(b) : callback(a, b)
1181
+ },
1182
+
1183
+ /**
1184
+ * Extend an object with another.
1185
+ *
1186
+ * @param {object} object
1187
+ * @param {object} other
1188
+ * @api public
1189
+ */
1190
+
1191
+ extend : function(object, other) {
1192
+ each(other, function(property, value){
1193
+ object[property] = value
1194
+ })
1195
+ },
1196
+
1197
+ /**
1198
+ * Iterate an object, invoking the given callback.
1199
+ *
1200
+ * @param {hash, array} object
1201
+ * @param {function} callback
1202
+ * @return {JSpec}
1203
+ * @api public
1204
+ */
1205
+
1206
+ each : function(object, callback) {
1207
+ if (object.constructor == Array)
1208
+ for (var i = 0, len = object.length; i < len; ++i)
1209
+ callIterator(callback, i, object[i])
1210
+ else
1211
+ for (var key in object)
1212
+ if (object.hasOwnProperty(key))
1213
+ callIterator(callback, key, object[key])
1214
+ },
1215
+
1216
+ /**
1217
+ * Iterate with memo.
1218
+ *
1219
+ * @param {hash, array} object
1220
+ * @param {object} memo
1221
+ * @param {function} callback
1222
+ * @return {object}
1223
+ * @api public
1224
+ */
1225
+
1226
+ inject : function(object, memo, callback) {
1227
+ each(object, function(key, value){
1228
+ memo = (callback.length == 2 ?
1229
+ callback(memo, value):
1230
+ callback(memo, key, value)) ||
1231
+ memo
1232
+ })
1233
+ return memo
1234
+ },
1235
+
1236
+ /**
1237
+ * Destub _object_'s _method_. When no _method_ is passed
1238
+ * all stubbed methods are destubbed. When no arguments
1239
+ * are passed every object found in JSpec.stubbed will be
1240
+ * destubbed.
1241
+ *
1242
+ * @param {mixed} object
1243
+ * @param {string} method
1244
+ * @api public
1245
+ */
1246
+
1247
+ destub : function(object, method) {
1248
+ if (method) {
1249
+ if (object['__prototype__' + method])
1250
+ delete object[method]
1251
+ else
1252
+ object[method] = object['__original__' + method]
1253
+ delete object['__prototype__' + method]
1254
+ delete object['__original____' + method]
1255
+ }
1256
+ else if (object) {
1257
+ for (var key in object)
1258
+ if (captures = key.match(/^(?:__prototype__|__original__)(.*)/))
1259
+ destub(object, captures[1])
1260
+ }
1261
+ else
1262
+ while (JSpec.stubbed.length)
1263
+ destub(JSpec.stubbed.shift())
1264
+ },
1265
+
1266
+ /**
1267
+ * Stub _object_'s _method_.
1268
+ *
1269
+ * stub(foo, 'toString').and_return('bar')
1270
+ *
1271
+ * @param {mixed} object
1272
+ * @param {string} method
1273
+ * @return {hash}
1274
+ * @api public
1275
+ */
1276
+
1277
+ stub : function(object, method) {
1278
+ hook('stubbing', object, method)
1279
+ JSpec.stubbed.push(object)
1280
+ var type = object.hasOwnProperty(method) ? '__original__' : '__prototype__'
1281
+ object[type + method] = object[method]
1282
+ object[method] = function(){}
1283
+ return {
1284
+ and_return : function(value) {
1285
+ if (typeof value == 'function') object[method] = value
1286
+ else object[method] = function(){ return value }
1287
+ }
1288
+ }
1289
+ },
1290
+
1291
+ /**
1292
+ * Map callback return values.
1293
+ *
1294
+ * @param {hash, array} object
1295
+ * @param {function} callback
1296
+ * @return {array}
1297
+ * @api public
1298
+ */
1299
+
1300
+ map : function(object, callback) {
1301
+ return inject(object, [], function(memo, key, value){
1302
+ memo.push(callIterator(callback, key, value))
1303
+ })
1304
+ },
1305
+
1306
+ /**
1307
+ * Returns the first matching expression or null.
1308
+ *
1309
+ * @param {hash, array} object
1310
+ * @param {function} callback
1311
+ * @return {mixed}
1312
+ * @api public
1313
+ */
1314
+
1315
+ any : function(object, callback) {
1316
+ return inject(object, null, function(state, key, value){
1317
+ if (state == undefined)
1318
+ return callIterator(callback, key, value) ? value : state
1319
+ })
1320
+ },
1321
+
1322
+ /**
1323
+ * Returns an array of values collected when the callback
1324
+ * given evaluates to true.
1325
+ *
1326
+ * @param {hash, array} object
1327
+ * @return {function} callback
1328
+ * @return {array}
1329
+ * @api public
1330
+ */
1331
+
1332
+ select : function(object, callback) {
1333
+ return inject(object, [], function(selected, key, value){
1334
+ if (callIterator(callback, key, value))
1335
+ selected.push(value)
1336
+ })
1337
+ },
1338
+
1339
+ /**
1340
+ * Define matchers.
1341
+ *
1342
+ * @param {hash} matchers
1343
+ * @api public
1344
+ */
1345
+
1346
+ addMatchers : function(matchers) {
1347
+ each(matchers, function(name, body){
1348
+ JSpec.addMatcher(name, body)
1349
+ })
1350
+ },
1351
+
1352
+ /**
1353
+ * Define a matcher.
1354
+ *
1355
+ * @param {string} name
1356
+ * @param {hash, function, string} body
1357
+ * @api public
1358
+ */
1359
+
1360
+ addMatcher : function(name, body) {
1361
+ hook('addingMatcher', name, body)
1362
+ if (name.indexOf(' ') != -1) {
1363
+ var matchers = name.split(/\s+/)
1364
+ var prefix = matchers.shift()
1365
+ each(matchers, function(name) {
1366
+ JSpec.addMatcher(prefix + '_' + name, body(name))
1367
+ })
1368
+ }
1369
+ this.matchers[name] = this.normalizeMatcherMessage(this.normalizeMatcherBody(body))
1370
+ this.matchers[name].name = name
1371
+ },
1372
+
1373
+ /**
1374
+ * Add a root suite to JSpec.
1375
+ *
1376
+ * @param {string} description
1377
+ * @param {body} function
1378
+ * @api public
1379
+ */
1380
+
1381
+ describe : function(description, body) {
1382
+ var suite = new JSpec.Suite(description, body, false)
1383
+ hook('addingSuite', suite)
1384
+ this.allSuites.push(suite)
1385
+ this.suites.push(suite)
1386
+ },
1387
+
1388
+ /**
1389
+ * Add a shared example suite to JSpec.
1390
+ *
1391
+ * @param {string} description
1392
+ * @param {body} function
1393
+ * @api public
1394
+ */
1395
+
1396
+ shared_behaviors_for : function(description, body) {
1397
+ var suite = new JSpec.Suite(description, body, true)
1398
+ hook('addingSuite', suite)
1399
+ this.allSuites.push(suite)
1400
+ this.suites.push(suite)
1401
+ },
1402
+
1403
+ /**
1404
+ * Return the contents of a function body.
1405
+ *
1406
+ * @param {function} body
1407
+ * @return {string}
1408
+ * @api public
1409
+ */
1410
+
1411
+ contentsOf : function(body) {
1412
+ return body.toString().match(/^[^\{]*{((.*\n*)*)}/m)[1]
1413
+ },
1414
+
1415
+ /**
1416
+ * Evaluate a JSpec capture body.
1417
+ *
1418
+ * @param {function} body
1419
+ * @param {string} errorMessage (optional)
1420
+ * @return {Type}
1421
+ * @api private
1422
+ */
1423
+
1424
+ evalBody : function(body, errorMessage) {
1425
+ var dsl = this.DSL || this.DSLs.snake
1426
+ var matchers = this.matchers
1427
+ var context = this.context || this.defaultContext
1428
+ var contents = this.contentsOf(body)
1429
+ hook('evaluatingBody', dsl, matchers, context, contents)
1430
+ with (dsl){ with (context) { with (matchers) { eval(contents) }}}
1431
+ },
1432
+
1433
+ /**
1434
+ * Pre-process a string of JSpec.
1435
+ *
1436
+ * @param {string} input
1437
+ * @return {string}
1438
+ * @api private
1439
+ */
1440
+
1441
+ preprocess : function(input) {
1442
+ if (typeof input != 'string') return
1443
+ input = hookImmutable('preprocessing', input)
1444
+ return input.
1445
+ replace(/\t/g, ' ').
1446
+ replace(/\r\n|\n|\r/g, '\n').
1447
+ split('__END__')[0].
1448
+ replace(/([\w\.]+)\.(stub|destub)\((.*?)\)$/gm, '$2($1, $3)').
1449
+ replace(/describe\s+(.*?)$/gm, 'describe($1, function(){').
1450
+ replace(/shared_behaviors_for\s+(.*?)$/gm, 'shared_behaviors_for($1, function(){').
1451
+ replace(/^\s+it\s+(.*?)$/gm, ' it($1, function(){').
1452
+ replace(/^ *(before_nested|after_nested|before_each|after_each|before|after)(?= |\n|$)/gm, 'JSpec.currentSuite.addHook("$1", function(){').
1453
+ replace(/^\s*end(?=\s|$)/gm, '});').
1454
+ replace(/-\{/g, 'function(){').
1455
+ replace(/(\d+)\.\.(\d+)/g, function(_, a, b){ return range(a, b) }).
1456
+ replace(/\.should([_\.]not)?[_\.](\w+)(?: |;|$)(.*)$/gm, '.should$1_$2($3)').
1457
+ replace(/([\/\s]*)(.+?)\.(should(?:[_\.]not)?)[_\.](\w+)\((.*)\)\s*;?$/gm, '$1 expect($2).$3($4, $5)').
1458
+ replace(/, \)/g, ')').
1459
+ replace(/should\.not/g, 'should_not')
1460
+ },
1461
+
1462
+ /**
1463
+ * Create a range string which can be evaluated to a native array.
1464
+ *
1465
+ * @param {int} start
1466
+ * @param {int} end
1467
+ * @return {string}
1468
+ * @api public
1469
+ */
1470
+
1471
+ range : function(start, end) {
1472
+ var current = parseInt(start), end = parseInt(end), values = [current]
1473
+ if (end > current) while (++current <= end) values.push(current)
1474
+ else while (--current >= end) values.push(current)
1475
+ return '[' + values + ']'
1476
+ },
1477
+
1478
+ /**
1479
+ * Report on the results.
1480
+ *
1481
+ * @api public
1482
+ */
1483
+
1484
+ report : function() {
1485
+ this.duration = Number(new Date) - this.start
1486
+ hook('reporting', JSpec.options)
1487
+ new (JSpec.options.reporter || JSpec.reporters.DOM)(JSpec, JSpec.options)
1488
+ },
1489
+
1490
+ /**
1491
+ * Run the spec suites. Options are merged
1492
+ * with JSpec options when present.
1493
+ *
1494
+ * @param {hash} options
1495
+ * @return {JSpec}
1496
+ * @api public
1497
+ */
1498
+
1499
+ run : function(options) {
1500
+ if (any(hook('running'), haveStopped)) return this
1501
+ if (options) extend(this.options, options)
1502
+ this.start = Number(new Date)
1503
+ each(this.suites, function(suite) { JSpec.runSuite(suite) })
1504
+ return this
1505
+ },
1506
+
1507
+ /**
1508
+ * Run a suite.
1509
+ *
1510
+ * @param {Suite} suite
1511
+ * @api public
1512
+ */
1513
+
1514
+ runSuite : function(suite) {
1515
+ if (!suite.isShared())
1516
+ {
1517
+ this.currentSuite = suite
1518
+ this.evalBody(suite.body)
1519
+ suite.ran = true
1520
+ hook('beforeSuite', suite), suite.hook('before'), suite.hook('before_nested')
1521
+ each(suite.specs, function(spec) {
1522
+ hook('beforeSpec', spec)
1523
+ suite.hook('before_each')
1524
+ JSpec.runSpec(spec)
1525
+ hook('afterSpec', spec)
1526
+ suite.hook('after_each')
1527
+ })
1528
+ if (suite.hasSuites()) {
1529
+ each(suite.suites, function(suite) {
1530
+ JSpec.runSuite(suite)
1531
+ })
1532
+ }
1533
+ hook('afterSuite', suite), suite.hook('after_nested'), suite.hook('after')
1534
+ this.stats.suitesFinished++
1535
+ }
1536
+ },
1537
+
1538
+ /**
1539
+ * Report a failure for the current spec.
1540
+ *
1541
+ * @param {string} message
1542
+ * @api public
1543
+ */
1544
+
1545
+ fail : function(message) {
1546
+ JSpec.currentSpec.fail(message)
1547
+ },
1548
+
1549
+ /**
1550
+ * Report a passing assertion for the current spec.
1551
+ *
1552
+ * @param {string} message
1553
+ * @api public
1554
+ */
1555
+
1556
+ pass : function(message) {
1557
+ JSpec.currentSpec.pass(message)
1558
+ },
1559
+
1560
+ /**
1561
+ * Run a spec.
1562
+ *
1563
+ * @param {Spec} spec
1564
+ * @api public
1565
+ */
1566
+
1567
+ runSpec : function(spec) {
1568
+ this.currentSpec = spec
1569
+ try { this.evalBody(spec.body) }
1570
+ catch (e) { fail(e) }
1571
+ spec.runDeferredAssertions()
1572
+ destub()
1573
+ this.stats.specsFinished++
1574
+ this.stats.assertions += spec.assertions.length
1575
+ },
1576
+
1577
+ /**
1578
+ * Require a dependency, with optional message.
1579
+ *
1580
+ * @param {string} dependency
1581
+ * @param {string} message (optional)
1582
+ * @return {JSpec}
1583
+ * @api public
1584
+ */
1585
+
1586
+ requires : function(dependency, message) {
1587
+ hook('requiring', dependency, message)
1588
+ try { eval(dependency) }
1589
+ catch (e) { throw 'JSpec depends on ' + dependency + ' ' + message }
1590
+ return this
1591
+ },
1592
+
1593
+ /**
1594
+ * Query against the current query strings keys
1595
+ * or the queryString specified.
1596
+ *
1597
+ * @param {string} key
1598
+ * @param {string} queryString
1599
+ * @return {string, null}
1600
+ * @api private
1601
+ */
1602
+
1603
+ query : function(key, queryString) {
1604
+ var queryString = (queryString || (main.location ? main.location.search : null) || '').substring(1)
1605
+ return inject(queryString.split('&'), null, function(value, pair){
1606
+ parts = pair.split('=')
1607
+ return parts[0] == key ? parts[1].replace(/%20|\+/gmi, ' ') : value
1608
+ })
1609
+ },
1610
+
1611
+ /**
1612
+ * Throw a JSpec related error.
1613
+ *
1614
+ * @param {string} message
1615
+ * @param {Exception} e
1616
+ * @api public
1617
+ */
1618
+
1619
+ error : function(message, e) {
1620
+ throw (message ? message : '') + e.toString() +
1621
+ (e.line ? ' near line ' + e.line : '')
1622
+ },
1623
+
1624
+ /**
1625
+ * Ad-hoc POST request for JSpec server usage.
1626
+ *
1627
+ * @param {string} uri
1628
+ * @param {string} data
1629
+ * @api private
1630
+ */
1631
+
1632
+ post : function(uri, data) {
1633
+ if (any(hook('posting', uri, data), haveStopped)) return
1634
+ var request = this.xhr()
1635
+ request.open('POST', uri, false)
1636
+ request.setRequestHeader('Content-Type', 'application/json')
1637
+ request.send(JSpec.JSON.encode(data))
1638
+ },
1639
+
1640
+ /**
1641
+ * Instantiate an XMLHttpRequest.
1642
+ *
1643
+ * Here we utilize IE's lame ActiveXObjects first which
1644
+ * allow IE access serve files via the file: protocol, otherwise
1645
+ * we then default to XMLHttpRequest.
1646
+ *
1647
+ * @return {XMLHttpRequest, ActiveXObject}
1648
+ * @api private
1649
+ */
1650
+
1651
+ xhr : function() {
1652
+ return this.ieXhr() || new JSpec.request
1653
+ },
1654
+
1655
+ /**
1656
+ * Return Microsoft piece of crap ActiveXObject.
1657
+ *
1658
+ * @return {ActiveXObject}
1659
+ * @api public
1660
+ */
1661
+
1662
+ ieXhr : function() {
1663
+ function object(str) {
1664
+ try { return new ActiveXObject(str) } catch(e) {}
1665
+ }
1666
+ return object('Msxml2.XMLHTTP.6.0') ||
1667
+ object('Msxml2.XMLHTTP.3.0') ||
1668
+ object('Msxml2.XMLHTTP') ||
1669
+ object('Microsoft.XMLHTTP')
1670
+ },
1671
+
1672
+ /**
1673
+ * Check for HTTP request support.
1674
+ *
1675
+ * @return {bool}
1676
+ * @api private
1677
+ */
1678
+
1679
+ hasXhr : function() {
1680
+ return JSpec.request || 'ActiveXObject' in main
1681
+ },
1682
+
1683
+ /**
1684
+ * Try loading _file_ returning the contents
1685
+ * string or null. Chain to locate / read a file.
1686
+ *
1687
+ * @param {string} file
1688
+ * @return {string}
1689
+ * @api public
1690
+ */
1691
+
1692
+ tryLoading : function(file) {
1693
+ try { return JSpec.load(file) } catch (e) {}
1694
+ },
1695
+
1696
+ /**
1697
+ * Load a _file_'s contents.
1698
+ *
1699
+ * @param {string} file
1700
+ * @param {function} callback
1701
+ * @return {string}
1702
+ * @api public
1703
+ */
1704
+
1705
+ load : function(file, callback) {
1706
+ if (any(hook('loading', file), haveStopped)) return
1707
+ if ('readFile' in main)
1708
+ return readFile(file)
1709
+ else if (this.hasXhr()) {
1710
+ var request = this.xhr()
1711
+ request.open('GET', file, false)
1712
+ request.send(null)
1713
+ if (request.readyState == 4 &&
1714
+ (request.status == 0 ||
1715
+ request.status.toString().charAt(0) == 2))
1716
+ return request.responseText
1717
+ }
1718
+ else
1719
+ throw new Error("failed to load `" + file + "'")
1720
+ },
1721
+
1722
+ /**
1723
+ * Load, pre-process, and evaluate a file.
1724
+ *
1725
+ * @param {string} file
1726
+ * @param {JSpec}
1727
+ * @api public
1728
+ */
1729
+
1730
+ exec : function(file) {
1731
+ if (any(hook('executing', file), haveStopped)) return this
1732
+ eval('with (JSpec){' + this.preprocess(this.load(file)) + '}')
1733
+ return this
1734
+ }
1735
+ }
1736
+
1737
+ // --- Node.js support
1738
+
1739
+ if (typeof GLOBAL === 'object' && typeof exports === 'object')
1740
+ quit = process.exit,
1741
+ print = require('sys').puts,
1742
+ readFile = require('fs').readFileSync
1743
+
1744
+ // --- Utility functions
1745
+
1746
+ var main = this,
1747
+ find = JSpec.any,
1748
+ utils = 'haveStopped stub hookImmutable hook destub map any last pass fail range each option inject select \
1749
+ error escape extend puts query strip color does addMatchers callIterator toArray equal'.split(/\s+/)
1750
+ while (utils.length) eval('var ' + utils[0] + ' = JSpec.' + utils.shift())
1751
+ if (!main.setTimeout) main.setTimeout = function(callback){ callback() }
1752
+
1753
+ // --- Matchers
1754
+
1755
+ addMatchers({
1756
+ equal : "===",
1757
+ eql : "equal(actual, expected)",
1758
+ be : "alias equal",
1759
+ be_greater_than : ">",
1760
+ be_less_than : "<",
1761
+ be_at_least : ">=",
1762
+ be_at_most : "<=",
1763
+ be_a : "actual.constructor == expected",
1764
+ be_an : "alias be_a",
1765
+ be_an_instance_of : "actual instanceof expected",
1766
+ be_null : "actual == null",
1767
+ be_true : "actual == true",
1768
+ be_false : "actual == false",
1769
+ be_undefined : "typeof actual == 'undefined'",
1770
+ be_type : "typeof actual == expected",
1771
+ match : "typeof actual == 'string' ? actual.match(expected) : false",
1772
+ respond_to : "typeof actual[expected] == 'function'",
1773
+ have_length : "actual.length == expected",
1774
+ be_within : "actual >= expected[0] && actual <= last(expected)",
1775
+ have_length_within : "actual.length >= expected[0] && actual.length <= last(expected)",
1776
+
1777
+ receive : { defer : true, match : function(actual, method, times) {
1778
+ proxy = new JSpec.ProxyAssertion(actual, method, times, this.negate)
1779
+ JSpec.currentSpec.assertions.push(proxy)
1780
+ return proxy
1781
+ }},
1782
+
1783
+ be_empty : function(actual) {
1784
+ if (actual.constructor == Object && actual.length == undefined)
1785
+ for (var key in actual)
1786
+ return false;
1787
+ return !actual.length
1788
+ },
1789
+
1790
+ include : function(actual) {
1791
+ for (state = true, i = 1; i < arguments.length; i++) {
1792
+ arg = arguments[i]
1793
+ switch (actual.constructor) {
1794
+ case String:
1795
+ case Number:
1796
+ case RegExp:
1797
+ case Function:
1798
+ state = actual.toString().indexOf(arg) !== -1
1799
+ break
1800
+
1801
+ case Object:
1802
+ state = arg in actual
1803
+ break
1804
+
1805
+ case Array:
1806
+ state = any(actual, function(value){ return equal(value, arg) })
1807
+ break
1808
+ }
1809
+ if (!state) return false
1810
+ }
1811
+ return true
1812
+ },
1813
+
1814
+ throw_error : { match : function(actual, expected, message) {
1815
+ try { actual() }
1816
+ catch (e) {
1817
+ this.e = e
1818
+ var assert = function(arg) {
1819
+ switch (arg.constructor) {
1820
+ case RegExp : return arg.test(e.message || e.toString())
1821
+ case String : return arg == (e.message || e.toString())
1822
+ case Function : return e instanceof arg || e.name == arg.name
1823
+ }
1824
+ }
1825
+ return message ? assert(expected) && assert(message) :
1826
+ expected ? assert(expected) :
1827
+ true
1828
+ }
1829
+ }, message : function(actual, expected, negate) {
1830
+ // TODO: refactor when actual is not in expected [0]
1831
+ var message_for = function(i) {
1832
+ if (expected[i] == undefined) return 'exception'
1833
+ switch (expected[i].constructor) {
1834
+ case RegExp : return 'exception matching ' + puts(expected[i])
1835
+ case String : return 'exception of ' + puts(expected[i])
1836
+ case Function : return expected[i].name || 'Error'
1837
+ }
1838
+ }
1839
+ exception = message_for(1) + (expected[2] ? ' and ' + message_for(2) : '')
1840
+ return 'expected ' + exception + (negate ? ' not ' : '' ) +
1841
+ ' to be thrown, but ' + (this.e ? 'got ' + puts(this.e) : 'nothing was')
1842
+ }},
1843
+
1844
+ have : function(actual, length, property) {
1845
+ return actual[property].length == length
1846
+ },
1847
+
1848
+ have_at_least : function(actual, length, property) {
1849
+ return actual[property].length >= length
1850
+ },
1851
+
1852
+ have_at_most :function(actual, length, property) {
1853
+ return actual[property].length <= length
1854
+ },
1855
+
1856
+ have_within : function(actual, range, property) {
1857
+ length = actual[property].length
1858
+ return length >= range.shift() && length <= range.pop()
1859
+ },
1860
+
1861
+ have_prop : function(actual, property, value) {
1862
+ return actual[property] == null ||
1863
+ actual[property] instanceof Function ? false:
1864
+ value == null ? true:
1865
+ does(actual[property], 'eql', value)
1866
+ },
1867
+
1868
+ have_property : function(actual, property, value) {
1869
+ return actual[property] == null ||
1870
+ actual[property] instanceof Function ? false:
1871
+ value == null ? true:
1872
+ value === actual[property]
1873
+ }
1874
+ })
1875
+
1876
+ })()