gusto 1.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/gusto +28 -0
- data/lib/HtmlReport.coffee +63 -0
- data/lib/Spec/DSL.coffee +119 -0
- data/lib/Spec/DelayedExpectation.coffee +61 -0
- data/lib/Spec/Matchers.coffee +69 -0
- data/lib/Spec/MethodStub/PossibleCall.coffee +81 -0
- data/lib/Spec/MethodStub.coffee +55 -0
- data/lib/Spec/MockObject.coffee +6 -0
- data/lib/Spec/ObjectDSL.coffee +26 -0
- data/lib/Spec/Report.coffee +23 -0
- data/lib/Spec/Suite.coffee +30 -0
- data/lib/Spec/Test.coffee +21 -0
- data/lib/Spec/Util.coffee +86 -0
- data/lib/Spec.coffee +62 -0
- data/lib/gusto/runner.rb +40 -0
- data/lib/gusto/server.rb +34 -0
- data/lib/gusto/version.rb +3 -0
- data/lib/gusto.rb +162 -0
- data/public/ie.js +5 -0
- data/public/jquery-1.5.2.js +8374 -0
- data/public/jsdiff.js +161 -0
- data/public/stacktrace.js +446 -0
- data/views/index.slim +19 -0
- metadata +180 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e33005d29c816fd1b04d2272759ed17e03d13931
|
4
|
+
data.tar.gz: 2142ceb53d542831d3044edece5c5d106db6d469
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 880e94182f4961bf38608dc257b44e0483622eb877469222575bd179a4476e24d7c7679862ab09a1485b4883ecb048e5b27b16a399e9ed30fb2ed91ae6786a17
|
7
|
+
data.tar.gz: ad17587ff01547549a8c6a983c3331745703300857334c79169a6ffde81b93ba3c5eb0033a03259c238990c0bc92b4cc260934c23cba62d821bcc31003fda325
|
data/bin/gusto
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
parser = OptionParser.new do |o|
|
8
|
+
o.banner = "Usage: #{$0} [options] mode\nPossible modes: server, cli, auto"
|
9
|
+
|
10
|
+
options[:port] = nil
|
11
|
+
o.on '-p', '--port PORT', 'Override server port' do |port|
|
12
|
+
options[:port] = port
|
13
|
+
end
|
14
|
+
|
15
|
+
options[:version] = false
|
16
|
+
o.on '-v', '--version', 'Show version' do
|
17
|
+
options[:version] = true
|
18
|
+
end
|
19
|
+
|
20
|
+
o.on_tail '-h', '--help', 'Display this screen' do
|
21
|
+
puts o
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
end
|
25
|
+
parser.parse!
|
26
|
+
|
27
|
+
require "#{File.dirname(__FILE__)}/../lib/gusto/runner.rb"
|
28
|
+
Gusto::Runner.new ARGV.first, options, parser
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class window.HtmlReport
|
2
|
+
constructor: (element) ->
|
3
|
+
@element = element
|
4
|
+
|
5
|
+
run: ->
|
6
|
+
root = new Spec.Suite()
|
7
|
+
for suite in Spec.suites
|
8
|
+
root.add suite
|
9
|
+
|
10
|
+
@report = root.run([])
|
11
|
+
@element.innerHTML = @html()
|
12
|
+
|
13
|
+
html: ->
|
14
|
+
@resultSummary(@report) + @testResults(@report)
|
15
|
+
|
16
|
+
resultSummary: (report) ->
|
17
|
+
"
|
18
|
+
<div class=\"result-summary\">
|
19
|
+
#{@resultSummaryCount 'total', report.counts[0] + report.counts[1] + report.counts[2]}
|
20
|
+
#{@resultSummaryCount 'passed', report.counts[0]}
|
21
|
+
#{@resultSummaryCount 'pending', report.counts[1]}
|
22
|
+
#{@resultSummaryCount 'failed', report.counts[2]}
|
23
|
+
</div>
|
24
|
+
"
|
25
|
+
|
26
|
+
resultSummaryCount: (name, count) ->
|
27
|
+
"
|
28
|
+
<div class=\"result-summary--count result-summary--#{name}\">
|
29
|
+
<span class=\"result-summary--label\">#{name.toUpperCase()}</span>
|
30
|
+
<span class=\"result-summary--number\">#{count}</span>
|
31
|
+
</div>
|
32
|
+
"
|
33
|
+
|
34
|
+
testResults: (report) ->
|
35
|
+
"
|
36
|
+
<div class=\"test-results\">
|
37
|
+
#{@testResultsReports report.subreports}
|
38
|
+
</div>
|
39
|
+
"
|
40
|
+
|
41
|
+
testResultsReports: (reports) ->
|
42
|
+
html = '<ul class=\"test-results--list\">'
|
43
|
+
for report in reports
|
44
|
+
html += @testResultsReport report
|
45
|
+
html + '</ul>'
|
46
|
+
|
47
|
+
testResultsReport: (report) ->
|
48
|
+
"
|
49
|
+
<li class=\"test-results--test test-results--test--#{@testResultsStatusClass report.status}\">
|
50
|
+
<div class=\"test-results--title\">#{report.title}</div>
|
51
|
+
#{if report.error then @testResultsErrorReport(report) else ''}
|
52
|
+
#{if report.subreports.length then @testResultsReports(report.subreports) else ''}
|
53
|
+
</li>
|
54
|
+
"
|
55
|
+
|
56
|
+
testResultsErrorReport: (report) ->
|
57
|
+
"<div class=\"test-results--error-message\">#{report.error}</div>"
|
58
|
+
|
59
|
+
testResultsStatusClass: (status) ->
|
60
|
+
switch status
|
61
|
+
when Spec.Report.Passed then 'passed'
|
62
|
+
when Spec.Report.Pending then 'pending'
|
63
|
+
when Spec.Report.Failed then 'failed'
|
data/lib/Spec/DSL.coffee
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
window.Spec ||= {}
|
2
|
+
|
3
|
+
window.Spec.DSL = DSL =
|
4
|
+
# Prepares a sub-test of the current test case
|
5
|
+
describe: (title, definition) ->
|
6
|
+
@__spec_definingSuite.add new Spec.Suite(title, definition)
|
7
|
+
|
8
|
+
# Adds a setup step to the current test case
|
9
|
+
before: (action) ->
|
10
|
+
@__spec_definingSuite.filter null, action
|
11
|
+
|
12
|
+
# Allows an assertion on a non-object value
|
13
|
+
expect: (object) ->
|
14
|
+
to: (matcher) ->
|
15
|
+
result = matcher(object)
|
16
|
+
throw new Spec.ExpectationError("expected #{result[1]}") unless result[0]
|
17
|
+
notTo: (matcher) ->
|
18
|
+
result = matcher(object)
|
19
|
+
throw new Spec.ExpectationError("expected not #{result[1]}") if result[0]
|
20
|
+
|
21
|
+
# Syntactic sugar to create a before method that prepares a variable
|
22
|
+
#
|
23
|
+
# Example:
|
24
|
+
# given 'dog', -> new Dog()
|
25
|
+
given: (name, definition) ->
|
26
|
+
@__spec_definingSuite.filter name, -> @[name] = definition.call this
|
27
|
+
|
28
|
+
# Creates a specificaition
|
29
|
+
it: (args...) ->
|
30
|
+
test = switch args.length
|
31
|
+
when 1
|
32
|
+
if typeof args[0] == 'function'
|
33
|
+
# Test with automatically generated title
|
34
|
+
new Spec.Test(Spec.Util.descriptionize(args[0]), args[0])
|
35
|
+
else
|
36
|
+
# Pending test
|
37
|
+
new Spec.Test(args[0], -> pending() )
|
38
|
+
when 2
|
39
|
+
# Test with manual title
|
40
|
+
new Spec.Test(args...)
|
41
|
+
@__spec_definingSuite.add test if test
|
42
|
+
|
43
|
+
pending: (message=null) ->
|
44
|
+
throw new Spec.PendingError(message)
|
45
|
+
|
46
|
+
# Creates a specification that tests an attribute of subject
|
47
|
+
#
|
48
|
+
# Example:
|
49
|
+
# subject -> new Employee('Fred')
|
50
|
+
# its 'name', -> should equal('Fred')
|
51
|
+
its: (attribute, definition) ->
|
52
|
+
root = this
|
53
|
+
it "#{attribute} #{Spec.Util.descriptionize definition}", ->
|
54
|
+
root.__spec_subject = @subject = Spec.Util.dereference @subject[attribute]
|
55
|
+
definition.call this
|
56
|
+
|
57
|
+
# Runs a test against @subject
|
58
|
+
#
|
59
|
+
# Example
|
60
|
+
# subject -> new Employee()
|
61
|
+
# it -> should beAnInstanceOf(Employee)
|
62
|
+
should: (matcher) ->
|
63
|
+
expect(@__spec_subject).to matcher
|
64
|
+
|
65
|
+
# Runs a negative test against @subject
|
66
|
+
#
|
67
|
+
# Example
|
68
|
+
# subject -> new Employee()
|
69
|
+
# it -> shouldNot be(null)
|
70
|
+
shouldNot: (matcher) ->
|
71
|
+
expect(@__spec_subject).notTo matcher
|
72
|
+
|
73
|
+
# Creates a new mock object
|
74
|
+
#
|
75
|
+
# Pass in a hash of method stubs to add to your mock.
|
76
|
+
#
|
77
|
+
# `mock(boots: 'cats')` gives an object that has the method:
|
78
|
+
# `boots: -> 'cats'`
|
79
|
+
#
|
80
|
+
# Optionally, you can pass a name to identify this mock as the
|
81
|
+
# first parameter.
|
82
|
+
#
|
83
|
+
mock: (args...) ->
|
84
|
+
name = args.shift() if typeof args[0] is 'string'
|
85
|
+
stubs = args.pop() || {}
|
86
|
+
new Spec.MockObject(name, stubs)
|
87
|
+
|
88
|
+
# Defines the subject of your test.
|
89
|
+
#
|
90
|
+
# Pass in a definition method which returns an object to be your
|
91
|
+
# test subject. This will be assigined to the instance variable
|
92
|
+
# @subject before your test runs. This subject will be used for
|
93
|
+
# any `should` or `shouldNot` tests you specify using the global
|
94
|
+
# `should` and `shouldNot` methods.
|
95
|
+
#
|
96
|
+
# subject -> new Client()
|
97
|
+
#
|
98
|
+
# it { should beA Client }
|
99
|
+
#
|
100
|
+
# Optionally, you can specify a name for your subject as the
|
101
|
+
# first parameter. This lets you call the subject by its name,
|
102
|
+
# making your tests more readable.
|
103
|
+
#
|
104
|
+
# subject 'foo', -> ...
|
105
|
+
#
|
106
|
+
# it 'gets prepared' ->
|
107
|
+
# @foo.prepare
|
108
|
+
# should 'bePrepared'
|
109
|
+
#
|
110
|
+
subject: (args...) ->
|
111
|
+
root = this
|
112
|
+
definition = args.pop()
|
113
|
+
name = args.pop()
|
114
|
+
before ->
|
115
|
+
root.__spec_subject = @subject = definition.call this
|
116
|
+
@[name] = @subject if name
|
117
|
+
|
118
|
+
DSL.context = DSL.describe
|
119
|
+
DSL.specify = DSL.it
|
@@ -0,0 +1,61 @@
|
|
1
|
+
window.Spec ||= {}
|
2
|
+
|
3
|
+
# A delayed expectation keeps track of an event that is expected to
|
4
|
+
# occur during the course of a test.
|
5
|
+
#
|
6
|
+
# To set a delayed expectation use window.expectation
|
7
|
+
class window.Spec.DelayedExpectation
|
8
|
+
@expectations: []
|
9
|
+
|
10
|
+
@add: (message) ->
|
11
|
+
exp = new Spec.DelayedExpectation(message)
|
12
|
+
@expectations.push exp
|
13
|
+
exp
|
14
|
+
|
15
|
+
@assert: ->
|
16
|
+
asserting = @expectations
|
17
|
+
@expectations = []
|
18
|
+
for expectation in asserting
|
19
|
+
expectation.assert()
|
20
|
+
|
21
|
+
@reset: ->
|
22
|
+
@expectations = []
|
23
|
+
|
24
|
+
constructor: (@message) ->
|
25
|
+
@met = 0
|
26
|
+
@desired = 1
|
27
|
+
|
28
|
+
# Specifies that this expectation must be met twice to count
|
29
|
+
# as a success.
|
30
|
+
twice: ->
|
31
|
+
@desired = 2
|
32
|
+
this
|
33
|
+
|
34
|
+
# Specifies how many times this expectation should be run to
|
35
|
+
# count as a success.
|
36
|
+
#
|
37
|
+
# Always use this with the format `.exactly(x).times` for better
|
38
|
+
# readability.
|
39
|
+
exactly: (count) ->
|
40
|
+
@desired = count
|
41
|
+
{times: this}
|
42
|
+
|
43
|
+
# Call when this expectation has been met.
|
44
|
+
meet: ->
|
45
|
+
@met += 1
|
46
|
+
|
47
|
+
# Raises an error unless this expecation has been met the right number of times.
|
48
|
+
assert: ->
|
49
|
+
unless @met is @desired
|
50
|
+
throw new Spec.ExpectationError("expected to #{@message} #{@_timesString @desired}, actually happened #{@_timesString @met}")
|
51
|
+
|
52
|
+
_timesString: (times) ->
|
53
|
+
switch times
|
54
|
+
when 0
|
55
|
+
'not at all'
|
56
|
+
when 1
|
57
|
+
'once'
|
58
|
+
when 2
|
59
|
+
'twice'
|
60
|
+
else
|
61
|
+
"#{times} times"
|
@@ -0,0 +1,69 @@
|
|
1
|
+
window.Spec ||= {}
|
2
|
+
|
3
|
+
window.Spec.Matchers =
|
4
|
+
# Tests if matched value === expected value
|
5
|
+
be: (expected) ->
|
6
|
+
(value) ->
|
7
|
+
[value is expected, "to be #{Spec.Util.inspect expected}, actual #{Spec.Util.inspect value}"]
|
8
|
+
|
9
|
+
# Tests that value type matches specified class
|
10
|
+
beA: (klass) ->
|
11
|
+
switch klass
|
12
|
+
when Boolean then _haveType 'boolean'
|
13
|
+
when Function then _haveType 'function'
|
14
|
+
when Number then _haveType 'number'
|
15
|
+
when String then _haveType 'string'
|
16
|
+
when Object then _haveType 'object'
|
17
|
+
else _beAnInstanceOf klass
|
18
|
+
|
19
|
+
# Tests if matched value == expected value
|
20
|
+
equal: (expected) ->
|
21
|
+
(value) ->
|
22
|
+
[String(value) == String(expected), "“#{String value}” to equal “#{String expected}” – #{$.trim diffString(String(value), String(expected))}"]
|
23
|
+
|
24
|
+
# All-purpose inclusion matcher
|
25
|
+
include: (expected) ->
|
26
|
+
if expected instanceof Array
|
27
|
+
(value) ->
|
28
|
+
match = true
|
29
|
+
for test in expected
|
30
|
+
match = false unless (value.indexOf && value.indexOf(test) >= 0) || value[test]?
|
31
|
+
[match, "to include #{Spec.Util.inspect expected}, actual #{Spec.Util.inspect value}"]
|
32
|
+
else if typeof expected == 'object'
|
33
|
+
(value) ->
|
34
|
+
missing = {}
|
35
|
+
match = true
|
36
|
+
for test of expected
|
37
|
+
if expected.hasOwnProperty test
|
38
|
+
unless value[test] isnt undefined && String(value[test]) == String(expected[test])
|
39
|
+
match = false
|
40
|
+
missing[test] = expected[test]
|
41
|
+
[match, "to include #{Spec.Util.inspect expected}, actual #{Spec.Util.inspect value}, missing #{Spec.Util.inspect missing}"]
|
42
|
+
else
|
43
|
+
include([expected])
|
44
|
+
|
45
|
+
# Tests if a function causes an error to be thrown when called
|
46
|
+
throwError: (message) ->
|
47
|
+
(fn) ->
|
48
|
+
thrown = false
|
49
|
+
try
|
50
|
+
fn()
|
51
|
+
catch e
|
52
|
+
thrown = e.message
|
53
|
+
finally
|
54
|
+
if thrown
|
55
|
+
return [thrown == message, "to throw an error with message “#{String thrown}”, actual message “#{String message}” – #{$.trim diffString(String(thrown), String(message))}"]
|
56
|
+
else
|
57
|
+
return [false, "to throw an error with message “#{message}”, no error thrown"]
|
58
|
+
|
59
|
+
# Tests a value type using typeof
|
60
|
+
_haveType: (type) ->
|
61
|
+
(value) ->
|
62
|
+
[typeof value is type, "to have type “#{type}”, actual “#{typeof value}”"]
|
63
|
+
|
64
|
+
# Tests if matched value is an instance of class
|
65
|
+
_beAnInstanceOf: (klass) ->
|
66
|
+
(value) ->
|
67
|
+
[value instanceof klass, "to be an instance of “#{klass}”"]
|
68
|
+
|
69
|
+
window.Spec.Matchers.beAn = window.Spec.Matchers.beA
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# Represents a possible call path for a MethodStub - that is a combination
|
2
|
+
# of expected arguments and return values.
|
3
|
+
class window.Spec.MethodStub.PossibleCall
|
4
|
+
constructor: (@original) ->
|
5
|
+
|
6
|
+
# Defines the expected arguments for this PossibleCall. By default a
|
7
|
+
# PossibleCall will accept any or no arguments. When you specify arguments
|
8
|
+
# here, the PossibleCall will respond only to those arguments, and a test
|
9
|
+
# failure will be recorded if it is called with incorrect arguments.
|
10
|
+
with: (args...) ->
|
11
|
+
@arguments = args
|
12
|
+
this
|
13
|
+
|
14
|
+
# Provides a return value for this PossibleCall
|
15
|
+
andReturn: (value) ->
|
16
|
+
@return = Spec.Util.reference(value)
|
17
|
+
this
|
18
|
+
|
19
|
+
# Causes this PossibleCall to pass through to the original method on the
|
20
|
+
# object before it was stubbed out.
|
21
|
+
andPassthrough: ->
|
22
|
+
@return = @original
|
23
|
+
|
24
|
+
# Sets an expectation that this PossibleCall be called as part of the test.
|
25
|
+
#
|
26
|
+
# This is used by window#shouldReceive, and doesn't need to be called
|
27
|
+
# directly.
|
28
|
+
expect: ->
|
29
|
+
# TODO: Make this description better, possibly with name of
|
30
|
+
# the method that should have been called
|
31
|
+
@expectation ||= Spec.DelayedExpectation.add('get called')
|
32
|
+
this
|
33
|
+
|
34
|
+
# Delegates to expectation
|
35
|
+
twice: ->
|
36
|
+
@expectation.twice()
|
37
|
+
this
|
38
|
+
|
39
|
+
# Delegates to expectation
|
40
|
+
exactly: (times) ->
|
41
|
+
@expectation.exactly times
|
42
|
+
{times: this}
|
43
|
+
|
44
|
+
# Checks if this PossibleCall matches the given array of arguments.
|
45
|
+
#
|
46
|
+
# A match is defined as having the same number of arguments, and each
|
47
|
+
# argument having an equal string representation to its counterpart.
|
48
|
+
matchesArguments: (args) ->
|
49
|
+
if !@arguments
|
50
|
+
true
|
51
|
+
else if @arguments.length isnt args.length
|
52
|
+
false
|
53
|
+
else
|
54
|
+
@_arraysMatch @arguments, args
|
55
|
+
|
56
|
+
# Causes this PossibleCall to fulfil a call on the stubbed method. Fails
|
57
|
+
# the test if the called arguments don't match expected arguments.
|
58
|
+
call: (object, method, args) ->
|
59
|
+
if @matchesArguments(args)
|
60
|
+
@expectation.meet() if @expectation
|
61
|
+
@return.apply object, args if @return
|
62
|
+
else
|
63
|
+
@_failOnInvalidArguments method, args
|
64
|
+
null
|
65
|
+
|
66
|
+
_arraysMatch: (a, b) ->
|
67
|
+
for i in [0..a.length]
|
68
|
+
return false if String(a[i]) isnt String(b[i])
|
69
|
+
true
|
70
|
+
|
71
|
+
_failOnInvalidArguments: (method, args) ->
|
72
|
+
throw new Spec.ExpectationError(
|
73
|
+
"expected ##{method} to be called#{@_argumentsString()}, " +
|
74
|
+
"actual arguments: “#{args.join ', '}”"
|
75
|
+
)
|
76
|
+
|
77
|
+
_argumentsString: ->
|
78
|
+
if @arguments
|
79
|
+
" with arguments “#{@arguments.join ', '}”"
|
80
|
+
else
|
81
|
+
''
|
@@ -0,0 +1,55 @@
|
|
1
|
+
window.Spec ||= {}
|
2
|
+
|
3
|
+
# Replaces a method on an object with a stub method, a fake method which can
|
4
|
+
# be configured to return predetermined responses, as well as set
|
5
|
+
# expectations on how it is called.
|
6
|
+
#
|
7
|
+
# These are created using window#stub, window#shouldReceive and
|
8
|
+
# window#shouldNotReceive. You shouldn't need to create one manually.
|
9
|
+
class window.Spec.MethodStub
|
10
|
+
@stubs: []
|
11
|
+
|
12
|
+
@reset: ->
|
13
|
+
while stub = @stubs.pop()
|
14
|
+
stub.remove()
|
15
|
+
|
16
|
+
constructor: (@object, @method) ->
|
17
|
+
@possibleCalls = []
|
18
|
+
@_replaceMethodOnObject()
|
19
|
+
Spec.MethodStub.stubs.push this
|
20
|
+
|
21
|
+
# Makes a new PossibleCall and adds it to the list
|
22
|
+
possibleCall: ->
|
23
|
+
call = new Spec.MethodStub.PossibleCall(@original)
|
24
|
+
@possibleCalls.unshift call
|
25
|
+
call
|
26
|
+
|
27
|
+
remove: ->
|
28
|
+
@object[@method] = @original
|
29
|
+
|
30
|
+
# Generates a new stub method to inject into the object, and sets the
|
31
|
+
# _stub property on it to point back to this MethodStub.
|
32
|
+
#
|
33
|
+
# _stub is used by window#stub to find an existing MethodStub for a method
|
34
|
+
# and add more possible calls to it, instead of writing over it with
|
35
|
+
# a new MethodStub.
|
36
|
+
#
|
37
|
+
# The code inside the stub method is the same for each MethodStub, but
|
38
|
+
# we create a fresh copy of it so we can assign a unique _stub property.
|
39
|
+
_stubMethod: ->
|
40
|
+
method = @method
|
41
|
+
stubMethod = (args...) ->
|
42
|
+
if call = arguments.callee._stub._findPossibleCall(args)
|
43
|
+
call.call this, method, args
|
44
|
+
|
45
|
+
stubMethod._stub = this
|
46
|
+
stubMethod
|
47
|
+
|
48
|
+
_findPossibleCall: (args) ->
|
49
|
+
for call in @possibleCalls
|
50
|
+
break if call.matchesArguments(arguments)
|
51
|
+
call
|
52
|
+
|
53
|
+
_replaceMethodOnObject: ->
|
54
|
+
@original = @object[@method]
|
55
|
+
@object[@method] = @_stubMethod()
|
@@ -0,0 +1,26 @@
|
|
1
|
+
window.Spec ||= {}
|
2
|
+
|
3
|
+
window.Spec.ObjectDSL =
|
4
|
+
# Stubs a method on object
|
5
|
+
stub: (method) ->
|
6
|
+
stub = if @[method] && @[method]._stub
|
7
|
+
@[method]._stub
|
8
|
+
else
|
9
|
+
new Spec.MethodStub(this, method)
|
10
|
+
stub.possibleCall()
|
11
|
+
|
12
|
+
# Tests for a positive match
|
13
|
+
should: (matcher) ->
|
14
|
+
expect(this).to matcher
|
15
|
+
|
16
|
+
# Tests for a negative match
|
17
|
+
shouldNot: (matcher) ->
|
18
|
+
expect(this).notTo matcher
|
19
|
+
|
20
|
+
# Creates a stub method with an expectation
|
21
|
+
shouldReceive: (method) ->
|
22
|
+
@stub(method).expect()
|
23
|
+
|
24
|
+
# Creates a stub method, with an expectation of no calls
|
25
|
+
shouldNotReceive: (name) ->
|
26
|
+
@shouldReceive(name).exactly(0).times
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class window.Spec.Report
|
2
|
+
@Passed: 0
|
3
|
+
@Pending: 1
|
4
|
+
@Failed: 2
|
5
|
+
|
6
|
+
constructor: (@title) ->
|
7
|
+
@status = Spec.Report.Passed
|
8
|
+
@error = null
|
9
|
+
@subreports = []
|
10
|
+
@counts = [0, 0, 0]
|
11
|
+
|
12
|
+
result: (result, error=null) ->
|
13
|
+
@status = result
|
14
|
+
@error = error
|
15
|
+
@counts[result]++
|
16
|
+
|
17
|
+
addSubreport: (subreport) ->
|
18
|
+
@subreports.push subreport
|
19
|
+
@counts[i] += subreport.counts[i] for i in [0..2]
|
20
|
+
@_updateStatus(subreport.status)
|
21
|
+
|
22
|
+
_updateStatus: (value) ->
|
23
|
+
@status = Math.max(@status, value)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class window.Spec.Suite
|
2
|
+
constructor: (@title, @definition) ->
|
3
|
+
@filters = []
|
4
|
+
@components = []
|
5
|
+
@loaded = false
|
6
|
+
|
7
|
+
load: ->
|
8
|
+
if @definition
|
9
|
+
window.__spec_definingSuite = this
|
10
|
+
@definition()
|
11
|
+
|
12
|
+
delete window.__spec_definingSuite
|
13
|
+
@loaded = true
|
14
|
+
|
15
|
+
add: (component) ->
|
16
|
+
@components.push component
|
17
|
+
|
18
|
+
filter: (name, definition) ->
|
19
|
+
@filters.push definition
|
20
|
+
|
21
|
+
run: (filters)->
|
22
|
+
@load() unless @loaded
|
23
|
+
allFilters = filters.concat(@filters)
|
24
|
+
report = new Spec.Report(@title)
|
25
|
+
report.status = Spec.Report.Pending unless @components.length
|
26
|
+
|
27
|
+
for component in @components
|
28
|
+
report.addSubreport component.run(allFilters)
|
29
|
+
|
30
|
+
report
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class window.Spec.Test
|
2
|
+
constructor: (@title, @definition) ->
|
3
|
+
|
4
|
+
run: (filters) ->
|
5
|
+
report = new Spec.Report(@title)
|
6
|
+
try
|
7
|
+
env = {}
|
8
|
+
filter.call(env) for filter in filters
|
9
|
+
@definition.call(env)
|
10
|
+
Spec.DelayedExpectation.assert()
|
11
|
+
report.result Spec.Report.Passed
|
12
|
+
catch error
|
13
|
+
report.result(
|
14
|
+
error.status || Spec.Report.Failed,
|
15
|
+
error.message
|
16
|
+
)
|
17
|
+
report.location = error.fileName + ':' + error.lineNumber
|
18
|
+
finally
|
19
|
+
Spec.DelayedExpectation.reset()
|
20
|
+
Spec.MethodStub.reset()
|
21
|
+
report
|
@@ -0,0 +1,86 @@
|
|
1
|
+
window.Spec ||= {}
|
2
|
+
|
3
|
+
window.Spec.Util =
|
4
|
+
extend: (object, extensions...) ->
|
5
|
+
for extension in extensions
|
6
|
+
for key, value of extension
|
7
|
+
object[key] = value
|
8
|
+
|
9
|
+
unextend: (object, extensions...) ->
|
10
|
+
for extension in extensions
|
11
|
+
for key, value of extension
|
12
|
+
delete object[key]
|
13
|
+
|
14
|
+
reference: (value) ->
|
15
|
+
if typeof value is 'function'
|
16
|
+
value
|
17
|
+
else
|
18
|
+
-> value
|
19
|
+
|
20
|
+
dereference: (value, context) ->
|
21
|
+
if typeof value is 'function'
|
22
|
+
value.call context
|
23
|
+
else
|
24
|
+
value
|
25
|
+
|
26
|
+
# Tries to format definition source code as readable test description
|
27
|
+
descriptionize: (definition) ->
|
28
|
+
# Get function source code
|
29
|
+
definition = String definition
|
30
|
+
|
31
|
+
# Remove function boilerplate from beginning
|
32
|
+
definition = definition.replace(/^\s*function\s*\([^\)]*\)\s*\{\s*(return\s*)?/, '')
|
33
|
+
|
34
|
+
# Remove function boilerplate from end
|
35
|
+
definition = definition.replace(/\s*;\s*\}\s*$/, '')
|
36
|
+
|
37
|
+
# Replace symbols with whitespace
|
38
|
+
definition = definition.replace(/[\s\(\)\{\}_\-\.'";]+/g, ' ')
|
39
|
+
|
40
|
+
# Split camelCased terms into seperate words
|
41
|
+
definition = definition.replace(/([a-z])([A-Z])/g, (s, a, b) -> "#{a} #{b.toLowerCase()}")
|
42
|
+
|
43
|
+
# Replace the word return with "it" (only for functions that are more complex than a simple return)
|
44
|
+
definition = definition.replace ' return ', ' it '
|
45
|
+
|
46
|
+
$.trim definition
|
47
|
+
|
48
|
+
# Returns an HTML representation of any kind of object
|
49
|
+
inspect: (object) ->
|
50
|
+
if object instanceof Array
|
51
|
+
s = '['
|
52
|
+
first = true
|
53
|
+
for item in object
|
54
|
+
if first
|
55
|
+
first = false
|
56
|
+
else
|
57
|
+
first += ', '
|
58
|
+
s += "“#{@escape(String(item))}”"
|
59
|
+
s + ']'
|
60
|
+
else if object is null
|
61
|
+
'null'
|
62
|
+
else if object is undefined
|
63
|
+
'undefined'
|
64
|
+
else if object is true
|
65
|
+
'true'
|
66
|
+
else if object is false
|
67
|
+
'false'
|
68
|
+
else if typeof object == 'object'
|
69
|
+
s = "{"
|
70
|
+
first = true
|
71
|
+
for key of object
|
72
|
+
# Access hasOwnProperty through Object.prototype to work around bug
|
73
|
+
# in IE6/7/8 when calling hasOwnProperty on a DOM element
|
74
|
+
if Object.prototype.hasOwnProperty.call(object, key)
|
75
|
+
if first
|
76
|
+
first = false
|
77
|
+
else
|
78
|
+
s += ", "
|
79
|
+
s += @escape(key) + ': “' + @escape(String(object[key])) + '”'
|
80
|
+
s + "}"
|
81
|
+
else
|
82
|
+
"“#{@escape(object)}”"
|
83
|
+
|
84
|
+
# Escapes text for HTML
|
85
|
+
escape: (string) ->
|
86
|
+
$('<div/>').text(String(string)).html()
|