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