gusto 1.0.0.beta2
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.
- 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()
|