visionmedia-jspec 1.1.7 → 2.0.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.
- data/History.rdoc +19 -0
- data/Manifest +6 -2
- data/README.rdoc +122 -86
- data/bin/jspec +1 -1
- data/jspec.gemspec +4 -4
- data/lib/images/loading.gif +0 -0
- data/lib/jspec.css +17 -2
- data/lib/jspec.jquery.js +4 -14
- data/lib/jspec.js +508 -154
- data/server/browsers.rb +3 -11
- data/server/server.rb +2 -13
- data/spec/server.html +5 -2
- data/spec/spec.grammar-less.js +34 -0
- data/spec/spec.grammar.js +27 -16
- data/spec/spec.html +6 -3
- data/spec/spec.jquery.js +32 -34
- data/spec/spec.js +86 -0
- data/spec/{spec.core.js → spec.matchers.js} +115 -107
- data/spec/spec.rhino.js +5 -1
- data/spec/spec.shared-behaviors.js +51 -0
- data/spec/spec.utils.js +138 -0
- metadata +9 -4
- data/spec/spec.core.dom.js +0 -12
data/lib/jspec.js
CHANGED
@@ -5,18 +5,15 @@
|
|
5
5
|
|
6
6
|
var JSpec = {
|
7
7
|
|
8
|
-
version
|
9
|
-
|
10
|
-
|
11
|
-
matchers
|
12
|
-
stats
|
13
|
-
options
|
8
|
+
version : '2.0.0',
|
9
|
+
suites : [],
|
10
|
+
allSuites : [],
|
11
|
+
matchers : {},
|
12
|
+
stats : { specs : 0, assertions : 0, failures : 0, passes : 0, specsFinished : 0, suitesFinished : 0 },
|
13
|
+
options : { profile : false },
|
14
14
|
|
15
15
|
/**
|
16
16
|
* Default context in which bodies are evaluated.
|
17
|
-
* This allows specs and hooks to use the 'this' keyword in
|
18
|
-
* order to store variables, as well as allowing the context
|
19
|
-
* to provide helper methods or properties.
|
20
17
|
*
|
21
18
|
* Replace context simply by setting JSpec.context
|
22
19
|
* to your own like below:
|
@@ -32,12 +29,40 @@
|
|
32
29
|
*
|
33
30
|
*/
|
34
31
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
32
|
+
defaultContext : {
|
33
|
+
|
34
|
+
/**
|
35
|
+
* Return an object used for proxy assertions.
|
36
|
+
* This object is used to indicate that an object
|
37
|
+
* should be an instance of _object_, not the constructor
|
38
|
+
* itself.
|
39
|
+
*
|
40
|
+
* @param {function} constructor
|
41
|
+
* @return {hash}
|
42
|
+
* @api public
|
43
|
+
*/
|
44
|
+
|
45
|
+
an_instance_of : function(constructor) {
|
46
|
+
return { an_instance_of : constructor }
|
47
|
+
},
|
48
|
+
|
49
|
+
/**
|
50
|
+
* Sets the current spec's wait duration to _n_.
|
51
|
+
*
|
52
|
+
* wait(3000)
|
53
|
+
* wait(1, 'second')
|
54
|
+
* wait(3, 'seconds')
|
55
|
+
*
|
56
|
+
* @param {number} n
|
57
|
+
* @param {string} unit
|
58
|
+
* @api public
|
59
|
+
*/
|
60
|
+
|
61
|
+
wait : function(n, unit) {
|
62
|
+
JSpec.currentSpec.wait = {
|
63
|
+
'second' : n * 1000,
|
64
|
+
'seconds' : n * 1000
|
65
|
+
}[unit] || n
|
41
66
|
}
|
42
67
|
},
|
43
68
|
|
@@ -60,14 +85,21 @@
|
|
60
85
|
report = document.getElementById(id)
|
61
86
|
failuresOnly = option('failuresOnly')
|
62
87
|
classes = results.stats.failures ? 'has-failures' : ''
|
63
|
-
if (!report)
|
88
|
+
if (!report) throw 'JSpec requires the element #' + id + ' to output its reports'
|
64
89
|
|
65
90
|
markup =
|
66
91
|
'<div id="jspec-report" class="' + classes + '"><div class="heading"> \
|
67
92
|
<span class="passes">Passes: <em>' + results.stats.passes + '</em></span> \
|
68
93
|
<span class="failures">Failures: <em>' + results.stats.failures + '</em></span> \
|
69
94
|
</div><table class="suites">'
|
70
|
-
|
95
|
+
|
96
|
+
bodyContents = function(body) {
|
97
|
+
return JSpec.
|
98
|
+
escape(JSpec.contentsOf(body)).
|
99
|
+
replace(/^ */gm, function(a){ return (new Array(Math.round(a.length / 3))).join(' ') }).
|
100
|
+
replace("\n", '<br/>')
|
101
|
+
}
|
102
|
+
|
71
103
|
renderSuite = function(suite) {
|
72
104
|
displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
|
73
105
|
if (displaySuite && suite.hasSpecs()) {
|
@@ -80,7 +112,7 @@
|
|
80
112
|
markup += '<td class="pass">' + spec.description+ '</td><td>' + spec.assertionsGraph() + '</td>'
|
81
113
|
else if(!spec.passed())
|
82
114
|
markup += '<td class="fail">' + spec.description + ' <em>' + spec.failure().message + '</em>' + '</td><td>' + spec.assertionsGraph() + '</td>'
|
83
|
-
markup += '<tr class="body"
|
115
|
+
markup += '<tr class="body"><td colspan="2"><pre>' + bodyContents(spec.body) + '</pre></td></tr>'
|
84
116
|
})
|
85
117
|
markup += '</tr>'
|
86
118
|
}
|
@@ -94,9 +126,7 @@
|
|
94
126
|
}
|
95
127
|
|
96
128
|
renderSuites(results.suites)
|
97
|
-
|
98
129
|
markup += '</table></div>'
|
99
|
-
|
100
130
|
report.innerHTML = markup
|
101
131
|
},
|
102
132
|
|
@@ -119,7 +149,7 @@
|
|
119
149
|
displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
|
120
150
|
if (displaySuite && suite.hasSpecs()) {
|
121
151
|
puts(color(' ' + suite.description, 'bold'))
|
122
|
-
|
152
|
+
each(suite.specs, function(spec){
|
123
153
|
assertionsGraph = inject(spec.assertions, '', function(graph, assertion){
|
124
154
|
return graph + color('.', assertion.passed ? 'green' : 'red')
|
125
155
|
})
|
@@ -127,9 +157,9 @@
|
|
127
157
|
puts(color(' ' + spec.description, 'blue') + assertionsGraph)
|
128
158
|
else if (spec.passed() && !failuresOnly)
|
129
159
|
puts(color(' ' + spec.description, 'green') + assertionsGraph)
|
130
|
-
else
|
160
|
+
else if (!spec.passed())
|
131
161
|
puts(color(' ' + spec.description, 'red') + assertionsGraph +
|
132
|
-
|
162
|
+
"\n" + indent(spec.failure().message) + "\n")
|
133
163
|
})
|
134
164
|
puts('')
|
135
165
|
}
|
@@ -158,7 +188,7 @@
|
|
158
188
|
renderSuite = function(suite) {
|
159
189
|
if (suite.ran) {
|
160
190
|
console.group(suite.description)
|
161
|
-
|
191
|
+
each(suite.specs, function(spec){
|
162
192
|
assertionCount = spec.assertions.length + ':'
|
163
193
|
if (spec.requiresImplementation())
|
164
194
|
console.warn(spec.description)
|
@@ -190,22 +220,141 @@
|
|
190
220
|
negate : negate,
|
191
221
|
matcher : matcher,
|
192
222
|
expected : expected,
|
193
|
-
|
194
|
-
|
223
|
+
|
224
|
+
// Report assertion results
|
225
|
+
|
226
|
+
report : function() {
|
227
|
+
this.passed ? JSpec.stats.passes++ : JSpec.stats.failures++
|
228
|
+
return this
|
195
229
|
},
|
196
230
|
|
197
|
-
|
231
|
+
// Run the assertion
|
232
|
+
|
233
|
+
run : function() {
|
198
234
|
// TODO: remove unshifting of expected
|
199
235
|
expected.unshift(actual == null ? null : actual.valueOf())
|
200
|
-
result = matcher.match.apply(JSpec, expected)
|
201
|
-
this.passed = negate ? !result : result
|
202
|
-
this.record(this.passed)
|
236
|
+
this.result = matcher.match.apply(JSpec, expected)
|
237
|
+
this.passed = negate ? !this.result : this.result
|
203
238
|
if (!this.passed) this.message = matcher.message(actual, expected, negate, matcher.name)
|
204
239
|
return this
|
205
240
|
}
|
206
241
|
})
|
207
242
|
},
|
208
243
|
|
244
|
+
ProxyAssertion : function(object, method, times) {
|
245
|
+
var self = this
|
246
|
+
var old = object[method]
|
247
|
+
|
248
|
+
// Proxy
|
249
|
+
|
250
|
+
object[method] = function(){
|
251
|
+
args = argumentsToArray(arguments)
|
252
|
+
result = old.apply(object, args)
|
253
|
+
self.calls.push({ args : args, result : result })
|
254
|
+
return result
|
255
|
+
}
|
256
|
+
|
257
|
+
// Times
|
258
|
+
|
259
|
+
this.times = {
|
260
|
+
'once' : 1,
|
261
|
+
'twice' : 2
|
262
|
+
}[times] || times || 1
|
263
|
+
|
264
|
+
// TODO: negation
|
265
|
+
|
266
|
+
extend(this, {
|
267
|
+
calls : [],
|
268
|
+
message : '',
|
269
|
+
defer : true,
|
270
|
+
passed : false,
|
271
|
+
object : object,
|
272
|
+
method : method,
|
273
|
+
|
274
|
+
// Proxy return value
|
275
|
+
|
276
|
+
and_return : function(result) {
|
277
|
+
this.expectedResult = result
|
278
|
+
return this
|
279
|
+
},
|
280
|
+
|
281
|
+
// Proxy arguments passed
|
282
|
+
|
283
|
+
with_args : function() {
|
284
|
+
this.expectedArgs = argumentsToArray(arguments)
|
285
|
+
return this
|
286
|
+
},
|
287
|
+
|
288
|
+
// Check if any calls have failing results
|
289
|
+
|
290
|
+
anyResultsFail : function() {
|
291
|
+
return any(this.calls, function(call){
|
292
|
+
return self.expectedResult.an_instance_of ?
|
293
|
+
call.result.constructor != self.expectedResult.an_instance_of:
|
294
|
+
hash(self.expectedResult) != hash(call.result)
|
295
|
+
})
|
296
|
+
},
|
297
|
+
|
298
|
+
// Return the failing result
|
299
|
+
|
300
|
+
failingResult : function() {
|
301
|
+
return this.anyResultsFail().result
|
302
|
+
},
|
303
|
+
|
304
|
+
// Check if any arguments fail
|
305
|
+
|
306
|
+
anyArgsFail : function() {
|
307
|
+
return any(this.calls, function(call){
|
308
|
+
return any(self.expectedArgs, function(i, arg){
|
309
|
+
return arg.an_instance_of ?
|
310
|
+
call.args[i].constructor != arg.an_instance_of:
|
311
|
+
hash(arg) != hash(call.args[i])
|
312
|
+
|
313
|
+
})
|
314
|
+
})
|
315
|
+
},
|
316
|
+
|
317
|
+
// Return the failing args
|
318
|
+
|
319
|
+
failingArgs : function() {
|
320
|
+
return this.anyArgsFail().args
|
321
|
+
},
|
322
|
+
|
323
|
+
// Report assertion results
|
324
|
+
|
325
|
+
report : function() {
|
326
|
+
this.passed ? JSpec.stats.passes++ : JSpec.stats.failures++
|
327
|
+
return this
|
328
|
+
},
|
329
|
+
|
330
|
+
// Run the assertion
|
331
|
+
|
332
|
+
run : function() {
|
333
|
+
methodString = 'expected ' + object.toString() + '.' + method + '()'
|
334
|
+
times = function(n) {
|
335
|
+
return n > 2 ? n + ' times' : { 1 : 'once', 2 : 'twice' }[n]
|
336
|
+
}
|
337
|
+
|
338
|
+
if (this.calls.length < this.times)
|
339
|
+
this.message = methodString + ' to be called ' + times(this.times) +
|
340
|
+
', but ' + (this.calls.length == 0 ? ' was not called' : ' was called ' + times(this.calls.length))
|
341
|
+
|
342
|
+
if (this.expectedResult && this.anyResultsFail())
|
343
|
+
this.message = methodString + ' to return ' + print(this.expectedResult) +
|
344
|
+
' but got ' + print(this.failingResult())
|
345
|
+
|
346
|
+
if (this.expectedArgs && this.anyArgsFail())
|
347
|
+
this.message = methodString + ' to be called with ' + print.apply(this, this.expectedArgs) +
|
348
|
+
' but was called with ' + print.apply(this, this.failingArgs())
|
349
|
+
|
350
|
+
if (!this.message.length)
|
351
|
+
this.passed = true
|
352
|
+
|
353
|
+
return this
|
354
|
+
}
|
355
|
+
})
|
356
|
+
},
|
357
|
+
|
209
358
|
/**
|
210
359
|
* Specification Suite block object.
|
211
360
|
*
|
@@ -215,6 +364,7 @@
|
|
215
364
|
*/
|
216
365
|
|
217
366
|
Suite : function(description, body) {
|
367
|
+
var self = this
|
218
368
|
extend(this, {
|
219
369
|
body: body,
|
220
370
|
description: description,
|
@@ -225,9 +375,10 @@
|
|
225
375
|
|
226
376
|
// Add a spec to the suite
|
227
377
|
|
228
|
-
|
378
|
+
addSpec : function(description, body) {
|
229
379
|
spec = new JSpec.Spec(description, body)
|
230
380
|
this.specs.push(spec)
|
381
|
+
JSpec.stats.specs++ // TODO: abstract
|
231
382
|
spec.suite = this
|
232
383
|
},
|
233
384
|
|
@@ -239,8 +390,10 @@
|
|
239
390
|
|
240
391
|
// Add a nested suite
|
241
392
|
|
242
|
-
|
393
|
+
addSuite : function(description, body) {
|
243
394
|
suite = new JSpec.Suite(description, body)
|
395
|
+
JSpec.allSuites.push(suite)
|
396
|
+
suite.name = suite.description
|
244
397
|
suite.description = this.description + ' ' + suite.description
|
245
398
|
this.suites.push(suite)
|
246
399
|
suite.suite = this
|
@@ -251,7 +404,7 @@
|
|
251
404
|
hook : function(hook) {
|
252
405
|
if (this.suite) this.suite.hook(hook)
|
253
406
|
each(this.hooks[hook], function(body) {
|
254
|
-
JSpec.evalBody(body, "Error in hook '" + hook + "', suite '" +
|
407
|
+
JSpec.evalBody(body, "Error in hook '" + hook + "', suite '" + self.description + "': ")
|
255
408
|
})
|
256
409
|
},
|
257
410
|
|
@@ -270,11 +423,9 @@
|
|
270
423
|
// Check if the entire suite passed
|
271
424
|
|
272
425
|
passed : function() {
|
273
|
-
|
274
|
-
|
275
|
-
if (!spec.passed()) passed = false
|
426
|
+
return !any(this.specs, function(spec){
|
427
|
+
return !spec.passed()
|
276
428
|
})
|
277
|
-
return passed
|
278
429
|
}
|
279
430
|
})
|
280
431
|
},
|
@@ -293,20 +444,27 @@
|
|
293
444
|
description : description,
|
294
445
|
assertions : [],
|
295
446
|
|
447
|
+
// Run deferred assertions
|
448
|
+
|
449
|
+
runDeferredAssertions : function() {
|
450
|
+
each(this.assertions, function(assertion){
|
451
|
+
if (assertion.defer) assertion.run().report()
|
452
|
+
})
|
453
|
+
},
|
454
|
+
|
296
455
|
// Find first failing assertion
|
297
456
|
|
298
457
|
failure : function() {
|
299
|
-
return
|
300
|
-
return !assertion.passed
|
458
|
+
return find(this.assertions, function(assertion){
|
459
|
+
return !assertion.passed
|
301
460
|
})
|
302
461
|
},
|
303
462
|
|
304
463
|
// Find all failing assertions
|
305
464
|
|
306
465
|
failures : function() {
|
307
|
-
return
|
308
|
-
|
309
|
-
return failures
|
466
|
+
return select(this.assertions, function(assertion){
|
467
|
+
return !assertion.passed
|
310
468
|
})
|
311
469
|
},
|
312
470
|
|
@@ -331,9 +489,103 @@
|
|
331
489
|
}
|
332
490
|
})
|
333
491
|
},
|
492
|
+
|
493
|
+
// --- DSLs
|
494
|
+
|
495
|
+
DSLs : {
|
496
|
+
snake : {
|
497
|
+
expect : function(actual){
|
498
|
+
return JSpec.expect(actual)
|
499
|
+
},
|
500
|
+
|
501
|
+
describe : function(description, body) {
|
502
|
+
return JSpec.currentSuite.addSuite(description, body)
|
503
|
+
},
|
504
|
+
|
505
|
+
it : function(description, body) {
|
506
|
+
return JSpec.currentSuite.addSpec(description, body)
|
507
|
+
},
|
508
|
+
|
509
|
+
before : function(body) {
|
510
|
+
return JSpec.currentSuite.addHook('before', body)
|
511
|
+
},
|
512
|
+
|
513
|
+
after : function(body) {
|
514
|
+
return JSpec.currentSuite.addHook('after', body)
|
515
|
+
},
|
516
|
+
|
517
|
+
before_each : function(body) {
|
518
|
+
return JSpec.currentSuite.addHook('before_each', body)
|
519
|
+
},
|
520
|
+
|
521
|
+
after_each : function(body) {
|
522
|
+
return JSpec.currentSuite.addHook('after_each', body)
|
523
|
+
},
|
524
|
+
|
525
|
+
should_behave_like : function(description) {
|
526
|
+
return JSpec.shareBehaviorsOf(description)
|
527
|
+
}
|
528
|
+
}
|
529
|
+
},
|
334
530
|
|
335
531
|
// --- Methods
|
336
532
|
|
533
|
+
/**
|
534
|
+
* Find a suite by its description or name.
|
535
|
+
*
|
536
|
+
* @param {string} description
|
537
|
+
* @return {Suite}
|
538
|
+
* @api private
|
539
|
+
*/
|
540
|
+
|
541
|
+
findSuite : function(description) {
|
542
|
+
return find(this.allSuites, function(suite){
|
543
|
+
return suite.name == description || suite.description == description
|
544
|
+
})
|
545
|
+
},
|
546
|
+
|
547
|
+
/**
|
548
|
+
* Share behaviors (specs) of the given suite with
|
549
|
+
* the current suite.
|
550
|
+
*
|
551
|
+
* @param {string} description
|
552
|
+
* @api public
|
553
|
+
*/
|
554
|
+
|
555
|
+
shareBehaviorsOf : function(description) {
|
556
|
+
if (suite = this.findSuite(description)) this.copySpecs(suite, this.currentSuite)
|
557
|
+
else throw 'failed to share behaviors. ' + print(description) + ' is not a valid Suite name'
|
558
|
+
},
|
559
|
+
|
560
|
+
/**
|
561
|
+
* Copy specs from one suite to another.
|
562
|
+
*
|
563
|
+
* @param {Suite} fromSuite
|
564
|
+
* @param {Suite} toSuite
|
565
|
+
* @api public
|
566
|
+
*/
|
567
|
+
|
568
|
+
copySpecs : function(fromSuite, toSuite) {
|
569
|
+
each(fromSuite.specs, function(spec){
|
570
|
+
toSuite.specs.push(spec)
|
571
|
+
})
|
572
|
+
},
|
573
|
+
|
574
|
+
/**
|
575
|
+
* Convert arguments to an array.
|
576
|
+
*
|
577
|
+
* @param {object} arguments
|
578
|
+
* @param {int} offset
|
579
|
+
* @return {array}
|
580
|
+
* @api public
|
581
|
+
*/
|
582
|
+
|
583
|
+
argumentsToArray : function(arguments, offset) {
|
584
|
+
args = []
|
585
|
+
for (i = 0; i < arguments.length; i++) args.push(arguments[i])
|
586
|
+
return args.slice(offset || 0)
|
587
|
+
},
|
588
|
+
|
337
589
|
/**
|
338
590
|
* Return ANSI-escaped colored string.
|
339
591
|
*
|
@@ -442,13 +694,14 @@
|
|
442
694
|
*/
|
443
695
|
|
444
696
|
hash : function(object) {
|
697
|
+
if (object == undefined) return 'undefined'
|
445
698
|
serialize = function(prefix) {
|
446
699
|
return inject(object, prefix + ':', function(buffer, key, value){
|
447
700
|
return buffer += hash(value)
|
448
701
|
})
|
449
702
|
}
|
450
703
|
switch (object.constructor) {
|
451
|
-
case Array:
|
704
|
+
case Array : return serialize('a')
|
452
705
|
case Object: return serialize('o')
|
453
706
|
case RegExp: return 'r:' + object.toString()
|
454
707
|
case Number: return 'n:' + object.toString()
|
@@ -479,28 +732,29 @@
|
|
479
732
|
|
480
733
|
print : function(object) {
|
481
734
|
if (arguments.length > 1) {
|
482
|
-
|
483
|
-
|
484
|
-
|
735
|
+
return map(argumentsToArray(arguments), function(arg){
|
736
|
+
return print(arg)
|
737
|
+
}).join(', ')
|
485
738
|
}
|
486
739
|
if (object === undefined) return ''
|
487
740
|
if (object === null) return 'null'
|
488
741
|
if (object === true) return 'true'
|
489
742
|
if (object === false) return 'false'
|
743
|
+
if (object.an_instance_of) return 'an instance of ' + object.an_instance_of.name
|
490
744
|
if (object.jquery && object.selector.length > 0) return 'selector ' + print(object.selector) + ''
|
491
745
|
if (object.jquery) return escape(object.html())
|
492
746
|
if (object.nodeName) return escape(object.outerHTML)
|
493
747
|
switch (object.constructor) {
|
494
748
|
case String: return "'" + escape(object) + "'"
|
495
749
|
case Number: return object
|
496
|
-
case Array :
|
497
|
-
|
498
|
-
|
499
|
-
|
750
|
+
case Array :
|
751
|
+
return inject(object, '[', function(b, v){
|
752
|
+
return b + ', ' + print(v)
|
753
|
+
}).replace('[,', '[') + ' ]'
|
500
754
|
case Object:
|
501
|
-
|
502
|
-
|
503
|
-
|
755
|
+
return inject(object, '{', function(b, k, v) {
|
756
|
+
return b + ', ' + print(k) + ' : ' + print(v)
|
757
|
+
}).replace('{,', '{') + ' }'
|
504
758
|
default:
|
505
759
|
return escape(object.toString())
|
506
760
|
}
|
@@ -515,13 +769,34 @@
|
|
515
769
|
*/
|
516
770
|
|
517
771
|
escape : function(html) {
|
518
|
-
|
519
|
-
return html.
|
772
|
+
return html.toString().
|
520
773
|
replace(/&/gmi, '&').
|
521
774
|
replace(/"/gmi, '"').
|
522
775
|
replace(/>/gmi, '>').
|
523
776
|
replace(/</gmi, '<')
|
524
777
|
},
|
778
|
+
|
779
|
+
/**
|
780
|
+
* Perform an assertion without reporting.
|
781
|
+
*
|
782
|
+
* This method is primarily used for internal
|
783
|
+
* matchers in order retain DRYness. May be invoked
|
784
|
+
* like below:
|
785
|
+
*
|
786
|
+
* does('foo', 'eql', 'foo')
|
787
|
+
* does([1,2], 'include', 1, 2)
|
788
|
+
*
|
789
|
+
* @param {mixed} actual
|
790
|
+
* @param {string} matcher
|
791
|
+
* @param {...} expected
|
792
|
+
* @return {mixed}
|
793
|
+
* @api private
|
794
|
+
*/
|
795
|
+
|
796
|
+
does : function(actual, matcher, expected) {
|
797
|
+
assertion = new JSpec.Assertion(JSpec.matchers[matcher], actual, argumentsToArray(arguments, 2))
|
798
|
+
return assertion.run().result
|
799
|
+
},
|
525
800
|
|
526
801
|
/**
|
527
802
|
* Perform an assertion.
|
@@ -536,20 +811,21 @@
|
|
536
811
|
*/
|
537
812
|
|
538
813
|
expect : function(actual) {
|
539
|
-
assert = function(
|
814
|
+
assert = function(matcher, args, negate) {
|
540
815
|
expected = []
|
541
816
|
for (i = 1; i < args.length; i++) expected.push(args[i])
|
542
|
-
assertion = new JSpec.Assertion(
|
543
|
-
|
544
|
-
|
817
|
+
assertion = new JSpec.Assertion(matcher, actual, expected, negate)
|
818
|
+
if (matcher.defer) assertion.run()
|
819
|
+
else JSpec.currentSpec.assertions.push(assertion.run().report())
|
820
|
+
return assertion.result
|
545
821
|
}
|
546
822
|
|
547
|
-
to = function(
|
548
|
-
return assert(
|
823
|
+
to = function(matcher) {
|
824
|
+
return assert(matcher, arguments, false)
|
549
825
|
}
|
550
826
|
|
551
|
-
not_to = function(
|
552
|
-
return assert(
|
827
|
+
not_to = function(matcher) {
|
828
|
+
return assert(matcher, arguments, true)
|
553
829
|
}
|
554
830
|
|
555
831
|
return {
|
@@ -560,45 +836,6 @@
|
|
560
836
|
}
|
561
837
|
},
|
562
838
|
|
563
|
-
/**
|
564
|
-
* Iterate an object, invoking the given callback.
|
565
|
-
*
|
566
|
-
* @param {hash, array, string} object
|
567
|
-
* @param {function} callback
|
568
|
-
* @return {JSpec}
|
569
|
-
* @api public
|
570
|
-
*/
|
571
|
-
|
572
|
-
each : function(object, callback) {
|
573
|
-
if (typeof object == 'string') object = object.split(' ')
|
574
|
-
for (key in object) {
|
575
|
-
if (object.hasOwnProperty(key))
|
576
|
-
callback.length == 1 ?
|
577
|
-
callback.call(JSpec, object[key]):
|
578
|
-
callback.call(JSpec, key, object[key])
|
579
|
-
}
|
580
|
-
return JSpec
|
581
|
-
},
|
582
|
-
|
583
|
-
/**
|
584
|
-
* Iterate with memo.
|
585
|
-
*
|
586
|
-
* @param {hash, array} object
|
587
|
-
* @param {object} initial
|
588
|
-
* @param {function} callback
|
589
|
-
* @return {object}
|
590
|
-
* @api public
|
591
|
-
*/
|
592
|
-
|
593
|
-
inject : function(object, initial, callback) {
|
594
|
-
each(object, function(key, value){
|
595
|
-
initial = callback.length == 2 ?
|
596
|
-
callback.call(JSpec, initial, value):
|
597
|
-
callback.call(JSpec, initial, key, value) || initial
|
598
|
-
})
|
599
|
-
return initial
|
600
|
-
},
|
601
|
-
|
602
839
|
/**
|
603
840
|
* Strim whitespace or chars.
|
604
841
|
*
|
@@ -614,6 +851,21 @@
|
|
614
851
|
replace(new RegExp('^[' + (chars || '\\s') + ']*'), '')
|
615
852
|
},
|
616
853
|
|
854
|
+
/**
|
855
|
+
* Call an iterator callback with arguments a, or b
|
856
|
+
* depending on the arity of the callback.
|
857
|
+
*
|
858
|
+
* @param {function} callback
|
859
|
+
* @param {mixed} a
|
860
|
+
* @param {mixed} b
|
861
|
+
* @return {mixed}
|
862
|
+
* @api private
|
863
|
+
*/
|
864
|
+
|
865
|
+
callIterator : function(callback, a, b) {
|
866
|
+
return callback.length == 1 ? callback(b) : callback(a, b)
|
867
|
+
},
|
868
|
+
|
617
869
|
/**
|
618
870
|
* Extend an object with another.
|
619
871
|
*
|
@@ -627,7 +879,43 @@
|
|
627
879
|
object[property] = value
|
628
880
|
})
|
629
881
|
},
|
630
|
-
|
882
|
+
|
883
|
+
/**
|
884
|
+
* Iterate an object, invoking the given callback.
|
885
|
+
*
|
886
|
+
* @param {hash, array, string} object
|
887
|
+
* @param {function} callback
|
888
|
+
* @return {JSpec}
|
889
|
+
* @api public
|
890
|
+
*/
|
891
|
+
|
892
|
+
each : function(object, callback) {
|
893
|
+
if (typeof object == 'string') object = object.split(' ')
|
894
|
+
for (key in object)
|
895
|
+
if (object.hasOwnProperty(key))
|
896
|
+
callIterator(callback, key, object[key])
|
897
|
+
},
|
898
|
+
|
899
|
+
/**
|
900
|
+
* Iterate with memo.
|
901
|
+
*
|
902
|
+
* @param {hash, array} object
|
903
|
+
* @param {object} memo
|
904
|
+
* @param {function} callback
|
905
|
+
* @return {object}
|
906
|
+
* @api public
|
907
|
+
*/
|
908
|
+
|
909
|
+
inject : function(object, memo, callback) {
|
910
|
+
each(object, function(key, value){
|
911
|
+
memo = (callback.length == 2 ?
|
912
|
+
callback(memo, value):
|
913
|
+
callback(memo, key, value)) ||
|
914
|
+
memo
|
915
|
+
})
|
916
|
+
return memo
|
917
|
+
},
|
918
|
+
|
631
919
|
/**
|
632
920
|
* Map callback return values.
|
633
921
|
*
|
@@ -639,27 +927,41 @@
|
|
639
927
|
|
640
928
|
map : function(object, callback) {
|
641
929
|
return inject(object, [], function(memo, key, value){
|
642
|
-
memo.push(callback
|
643
|
-
callback.call(JSpec, value):
|
644
|
-
callback.call(JSpec, key, value))
|
930
|
+
memo.push(callIterator(callback, key, value))
|
645
931
|
})
|
646
932
|
},
|
647
933
|
|
648
934
|
/**
|
649
|
-
* Returns
|
935
|
+
* Returns the first matching expression or null.
|
650
936
|
*
|
651
937
|
* @param {hash, array} object
|
652
938
|
* @param {function} callback
|
653
|
-
* @return {
|
939
|
+
* @return {mixed}
|
654
940
|
* @api public
|
655
941
|
*/
|
656
942
|
|
657
943
|
any : function(object, callback) {
|
658
|
-
return inject(object,
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
944
|
+
return inject(object, null, function(state, key, value){
|
945
|
+
return state ? state :
|
946
|
+
callIterator(callback, key, value) ?
|
947
|
+
value : state
|
948
|
+
})
|
949
|
+
},
|
950
|
+
|
951
|
+
/**
|
952
|
+
* Returns an array of values collected when the callback
|
953
|
+
* given evaluates to true.
|
954
|
+
*
|
955
|
+
* @param {hash, array} object
|
956
|
+
* @return {function} callback
|
957
|
+
* @return {array}
|
958
|
+
* @api public
|
959
|
+
*/
|
960
|
+
|
961
|
+
select : function(object, callback) {
|
962
|
+
return inject(object, [], function(memo, key, value){
|
963
|
+
if (callIterator(callback, key, value))
|
964
|
+
memo.push(value)
|
663
965
|
})
|
664
966
|
},
|
665
967
|
|
@@ -667,15 +969,13 @@
|
|
667
969
|
* Define matchers.
|
668
970
|
*
|
669
971
|
* @param {hash} matchers
|
670
|
-
* @return {JSpec}
|
671
972
|
* @api public
|
672
973
|
*/
|
673
974
|
|
674
975
|
addMatchers : function(matchers) {
|
675
976
|
each(matchers, function(name, body){
|
676
|
-
|
977
|
+
JSpec.addMatcher(name, body)
|
677
978
|
})
|
678
|
-
return this
|
679
979
|
},
|
680
980
|
|
681
981
|
/**
|
@@ -683,14 +983,12 @@
|
|
683
983
|
*
|
684
984
|
* @param {string} name
|
685
985
|
* @param {hash, function, string} body
|
686
|
-
* @return {JSpec}
|
687
986
|
* @api public
|
688
987
|
*/
|
689
988
|
|
690
989
|
addMatcher : function(name, body) {
|
691
990
|
this.matchers[name] = this.normalizeMatcherMessage(this.normalizeMatcherBody(body))
|
692
991
|
this.matchers[name].name = name
|
693
|
-
return this
|
694
992
|
},
|
695
993
|
|
696
994
|
/**
|
@@ -698,13 +996,25 @@
|
|
698
996
|
*
|
699
997
|
* @param {string} description
|
700
998
|
* @param {body} function
|
701
|
-
* @return {JSpec}
|
702
999
|
* @api public
|
703
1000
|
*/
|
704
1001
|
|
705
1002
|
describe : function(description, body) {
|
706
|
-
|
707
|
-
|
1003
|
+
suite = new JSpec.Suite(description, body)
|
1004
|
+
this.allSuites.push(suite)
|
1005
|
+
this.suites.push(suite)
|
1006
|
+
},
|
1007
|
+
|
1008
|
+
/**
|
1009
|
+
* Return the contents of a function body.
|
1010
|
+
*
|
1011
|
+
* @param {function} body
|
1012
|
+
* @return {string}
|
1013
|
+
* @api public
|
1014
|
+
*/
|
1015
|
+
|
1016
|
+
contentsOf : function(body) {
|
1017
|
+
return body.toString().match(/^[^\{]*{((.*\n*)*)}/m)[1]
|
708
1018
|
},
|
709
1019
|
|
710
1020
|
/**
|
@@ -717,7 +1027,11 @@
|
|
717
1027
|
*/
|
718
1028
|
|
719
1029
|
evalBody : function(body, errorMessage) {
|
720
|
-
|
1030
|
+
dsl = this.DSL || this.DSLs.snake
|
1031
|
+
matchers = this.matchers
|
1032
|
+
context = this.context || this.defaultContext
|
1033
|
+
contents = this.contentsOf(body)
|
1034
|
+
try { eval('with (dsl){ with (context) { with (matchers) { ' + contents + ' }}}') }
|
721
1035
|
catch(e) { error(errorMessage, e) }
|
722
1036
|
},
|
723
1037
|
|
@@ -731,16 +1045,14 @@
|
|
731
1045
|
|
732
1046
|
preprocess : function(input) {
|
733
1047
|
return input.
|
734
|
-
replace(/describe (.*?)$/
|
735
|
-
replace(/
|
736
|
-
replace(
|
737
|
-
replace(/^(?: *)(before_each|after_each|before|after)(?= |\n|$)/gm, 'this.addHook("$1", function(){').
|
1048
|
+
replace(/describe (.*?)$/gm, 'describe($1, function(){').
|
1049
|
+
replace(/ it (.*?)$/gm, ' it($1, function(){').
|
1050
|
+
replace(/^(?: *)(before_each|after_each|before|after)(?= |\n|$)/gm, 'JSpec.currentSuite.addHook("$1", function(){').
|
738
1051
|
replace(/end(?= |\n|$)/gm, '});').
|
739
1052
|
replace(/-\{/g, 'function(){').
|
740
1053
|
replace(/(\d+)\.\.(\d+)/g, function(_, a, b){ return range(a, b) }).
|
741
|
-
replace(/([\s\(]+)\./gm, '$1this.').
|
742
1054
|
replace(/\.should([_\.]not)?[_\.](\w+)(?: |$)(.*)$/gm, '.should$1_$2($3)').
|
743
|
-
replace(/([\/ ]*)(.+?)\.(should(?:[_\.]not)?)[_\.](\w+)\((.*)\)$/gm, '$1 expect($2).$3(
|
1055
|
+
replace(/([\/ ]*)(.+?)\.(should(?:[_\.]not)?)[_\.](\w+)\((.*)\)$/gm, '$1 expect($2).$3($4, $5)').
|
744
1056
|
replace(/, \)/gm, ')').
|
745
1057
|
replace(/should\.not/gm, 'should_not')
|
746
1058
|
},
|
@@ -760,6 +1072,18 @@
|
|
760
1072
|
else while (--current >= end) values.push(current)
|
761
1073
|
return '[' + values + ']'
|
762
1074
|
},
|
1075
|
+
|
1076
|
+
/**
|
1077
|
+
* Call _callback_ when all specs have finished.
|
1078
|
+
*
|
1079
|
+
* @param {function} callback
|
1080
|
+
* @api public
|
1081
|
+
*/
|
1082
|
+
|
1083
|
+
whenFinished : function(callback) {
|
1084
|
+
if (this.stats.specsFinished >= this.stats.specs) callback()
|
1085
|
+
else setTimeout(function(){ JSpec.whenFinished(callback) }, 50)
|
1086
|
+
},
|
763
1087
|
|
764
1088
|
/**
|
765
1089
|
* Report on the results.
|
@@ -768,9 +1092,11 @@
|
|
768
1092
|
*/
|
769
1093
|
|
770
1094
|
report : function() {
|
771
|
-
this.
|
772
|
-
|
773
|
-
new
|
1095
|
+
this.whenFinished(function() {
|
1096
|
+
JSpec.options.formatter ?
|
1097
|
+
new JSpec.options.formatter(JSpec, JSpec.options):
|
1098
|
+
new JSpec.formatters.DOM(JSpec, JSpec.options)
|
1099
|
+
})
|
774
1100
|
},
|
775
1101
|
|
776
1102
|
/**
|
@@ -785,35 +1111,51 @@
|
|
785
1111
|
run : function(options) {
|
786
1112
|
if (options) extend(this.options, options)
|
787
1113
|
if (option('profile')) console.group('Profile')
|
788
|
-
each(this.suites, function(suite) {
|
1114
|
+
each(this.suites, function(suite) { JSpec.runSuite(suite) })
|
789
1115
|
if (option('profile')) console.groupEnd()
|
790
1116
|
return this
|
791
1117
|
},
|
1118
|
+
|
1119
|
+
/**
|
1120
|
+
* When the current spec's wait duration has passed
|
1121
|
+
* the _callback_ will be called.
|
1122
|
+
*
|
1123
|
+
* @param {function} callback
|
1124
|
+
* @api public
|
1125
|
+
*/
|
1126
|
+
|
1127
|
+
whenCurrentSpecIsFinished : function(callback) {
|
1128
|
+
if (this.currentSpec && this.currentSpec.wait)
|
1129
|
+
setTimeout(callback, this.currentSpec.wait)
|
1130
|
+
else callback()
|
1131
|
+
},
|
792
1132
|
|
793
1133
|
/**
|
794
1134
|
* Run a suite.
|
795
1135
|
*
|
796
1136
|
* @param {Suite} suite
|
797
|
-
* @return {JSpec}
|
798
1137
|
* @api public
|
799
1138
|
*/
|
800
1139
|
|
801
1140
|
runSuite : function(suite) {
|
802
|
-
suite
|
1141
|
+
this.currentSuite = suite
|
1142
|
+
this.evalBody(suite.body)
|
803
1143
|
suite.ran = true
|
804
1144
|
suite.hook('before')
|
805
1145
|
each(suite.specs, function(spec) {
|
806
|
-
|
807
|
-
|
808
|
-
|
1146
|
+
JSpec.whenCurrentSpecIsFinished(function(){
|
1147
|
+
suite.hook('before_each')
|
1148
|
+
JSpec.runSpec(spec)
|
1149
|
+
suite.hook('after_each')
|
1150
|
+
})
|
809
1151
|
})
|
810
1152
|
if (suite.hasSuites()) {
|
811
1153
|
each(suite.suites, function(suite) {
|
812
|
-
|
1154
|
+
JSpec.runSuite(suite)
|
813
1155
|
})
|
814
1156
|
}
|
815
1157
|
suite.hook('after')
|
816
|
-
|
1158
|
+
this.stats.suitesFinished++
|
817
1159
|
},
|
818
1160
|
|
819
1161
|
/**
|
@@ -837,11 +1179,12 @@
|
|
837
1179
|
|
838
1180
|
runSpec : function(spec) {
|
839
1181
|
this.currentSpec = spec
|
840
|
-
this.stats.specs++
|
841
1182
|
if (option('profile')) console.time(spec.description)
|
842
1183
|
try { this.evalBody(spec.body) }
|
843
1184
|
catch (e) { fail(e) }
|
1185
|
+
spec.runDeferredAssertions()
|
844
1186
|
if (option('profile')) console.timeEnd(spec.description)
|
1187
|
+
this.stats.specsFinished++
|
845
1188
|
this.stats.assertions += spec.assertions.length
|
846
1189
|
},
|
847
1190
|
|
@@ -886,8 +1229,7 @@
|
|
886
1229
|
|
887
1230
|
error : function(message, e) {
|
888
1231
|
throw (message ? message : '') + e.toString() +
|
889
|
-
(e.line ? ' near line ' + e.line : '')
|
890
|
-
(JSpec.file ? ' in ' + JSpec.file : '')
|
1232
|
+
(e.line ? ' near line ' + e.line : '')
|
891
1233
|
},
|
892
1234
|
|
893
1235
|
/**
|
@@ -912,8 +1254,10 @@
|
|
912
1254
|
*/
|
913
1255
|
|
914
1256
|
reportToServer : function() {
|
915
|
-
|
916
|
-
|
1257
|
+
this.whenFinished(function(){
|
1258
|
+
JSpec.post('http://localhost:4444', 'passes=' + JSpec.stats.passes + '&failures=' + JSpec.stats.failures)
|
1259
|
+
if ('close' in main) main.close()
|
1260
|
+
})
|
917
1261
|
},
|
918
1262
|
|
919
1263
|
/**
|
@@ -924,7 +1268,7 @@
|
|
924
1268
|
*/
|
925
1269
|
|
926
1270
|
xhr : function() {
|
927
|
-
return
|
1271
|
+
return 'ActiveXObject' in main ?
|
928
1272
|
new ActiveXObject("Microsoft.XMLHTTP"):
|
929
1273
|
new XMLHttpRequest()
|
930
1274
|
},
|
@@ -949,7 +1293,6 @@
|
|
949
1293
|
*/
|
950
1294
|
|
951
1295
|
load : function(file) {
|
952
|
-
this.file = file
|
953
1296
|
if (this.hasXhr()) {
|
954
1297
|
request = this.xhr()
|
955
1298
|
request.open('GET', file, false)
|
@@ -971,7 +1314,7 @@
|
|
971
1314
|
*/
|
972
1315
|
|
973
1316
|
exec : function(file) {
|
974
|
-
eval(this.preprocess(this.load(file)))
|
1317
|
+
eval('with (JSpec){' + this.preprocess(this.load(file)) + '}')
|
975
1318
|
return this
|
976
1319
|
}
|
977
1320
|
}
|
@@ -979,15 +1322,17 @@
|
|
979
1322
|
// --- Utility functions
|
980
1323
|
|
981
1324
|
var main = this
|
982
|
-
var puts = print
|
1325
|
+
var puts = main.print
|
983
1326
|
var map = JSpec.map
|
984
1327
|
var any = JSpec.any
|
1328
|
+
var find = JSpec.any
|
985
1329
|
var last = JSpec.last
|
986
1330
|
var fail = JSpec.fail
|
987
1331
|
var range = JSpec.range
|
988
1332
|
var each = JSpec.each
|
989
1333
|
var option = JSpec.option
|
990
1334
|
var inject = JSpec.inject
|
1335
|
+
var select = JSpec.select
|
991
1336
|
var error = JSpec.error
|
992
1337
|
var escape = JSpec.escape
|
993
1338
|
var extend = JSpec.extend
|
@@ -996,8 +1341,11 @@
|
|
996
1341
|
var query = JSpec.query
|
997
1342
|
var strip = JSpec.strip
|
998
1343
|
var color = JSpec.color
|
999
|
-
var
|
1344
|
+
var does = JSpec.does
|
1000
1345
|
var addMatchers = JSpec.addMatchers
|
1346
|
+
var callIterator = JSpec.callIterator
|
1347
|
+
var argumentsToArray = JSpec.argumentsToArray
|
1348
|
+
if (!main.setTimeout) main.setTimeout = function(callback){ callback() }
|
1001
1349
|
|
1002
1350
|
// --- Matchers
|
1003
1351
|
|
@@ -1028,6 +1376,12 @@
|
|
1028
1376
|
hash(actual) == hash(expected):
|
1029
1377
|
actual == expected
|
1030
1378
|
},
|
1379
|
+
|
1380
|
+
receive : { defer : true, match : function(actual, method, times) {
|
1381
|
+
proxy = new JSpec.ProxyAssertion(actual, method, times)
|
1382
|
+
JSpec.currentSpec.assertions.push(proxy)
|
1383
|
+
return proxy
|
1384
|
+
}},
|
1031
1385
|
|
1032
1386
|
include : function(actual) {
|
1033
1387
|
for (state = true, i = 1; i < arguments.length; i++) {
|
@@ -1058,9 +1412,9 @@
|
|
1058
1412
|
catch (e) {
|
1059
1413
|
if (expected == undefined) return true
|
1060
1414
|
switch (expected.constructor) {
|
1061
|
-
case RegExp:
|
1062
|
-
case Function: return e instanceof expected
|
1063
|
-
case String:
|
1415
|
+
case RegExp : return expected.test(e)
|
1416
|
+
case Function : return e instanceof expected
|
1417
|
+
case String : return expected == e.toString()
|
1064
1418
|
}
|
1065
1419
|
}
|
1066
1420
|
},
|
@@ -1086,7 +1440,7 @@
|
|
1086
1440
|
return actual[property] == null ||
|
1087
1441
|
actual[property] instanceof Function ? false:
|
1088
1442
|
value == null ? true:
|
1089
|
-
|
1443
|
+
does(actual[property], 'eql', value)
|
1090
1444
|
},
|
1091
1445
|
|
1092
1446
|
have_property : function(actual, property, value) {
|
@@ -1101,4 +1455,4 @@
|
|
1101
1455
|
|
1102
1456
|
this.JSpec = JSpec
|
1103
1457
|
|
1104
|
-
})()
|
1458
|
+
})()
|