bootcamp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ })()