rspec-json_matchers 0.1.0.alpha.1
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/.gitignore +12 -0
- data/.travis.yml +20 -0
- data/Appraisals +16 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +497 -0
- data/Rakefile +14 -0
- data/gemfiles/rspec_3_0.gemfile +7 -0
- data/gemfiles/rspec_3_1.gemfile +7 -0
- data/gemfiles/rspec_3_2.gemfile +7 -0
- data/gemfiles/rspec_3_3.gemfile +7 -0
- data/lib/rspec/json_matchers/comparers/abstract_comparer.rb +289 -0
- data/lib/rspec/json_matchers/comparers/comparison_result.rb +22 -0
- data/lib/rspec/json_matchers/comparers/exact_keys_comparer.rb +27 -0
- data/lib/rspec/json_matchers/comparers/include_keys_comparer.rb +27 -0
- data/lib/rspec/json_matchers/comparers.rb +13 -0
- data/lib/rspec/json_matchers/expectation.rb +78 -0
- data/lib/rspec/json_matchers/expectations/abstract.rb +36 -0
- data/lib/rspec/json_matchers/expectations/core.rb +103 -0
- data/lib/rspec/json_matchers/expectations/mixins/built_in.rb +177 -0
- data/lib/rspec/json_matchers/expectations/private.rb +181 -0
- data/lib/rspec/json_matchers/expectations.rb +14 -0
- data/lib/rspec/json_matchers/matchers/be_json_matcher.rb +92 -0
- data/lib/rspec/json_matchers/matchers/be_json_with_content_matcher.rb +21 -0
- data/lib/rspec/json_matchers/matchers/be_json_with_sizes_matcher.rb +21 -0
- data/lib/rspec/json_matchers/matchers/be_json_with_something_matcher.rb +174 -0
- data/lib/rspec/json_matchers/matchers.rb +12 -0
- data/lib/rspec/json_matchers/utils/collection_keys_extractor.rb +35 -0
- data/lib/rspec/json_matchers/utils/key_path/extraction_result.rb +22 -0
- data/lib/rspec/json_matchers/utils/key_path/extractor.rb +70 -0
- data/lib/rspec/json_matchers/utils/key_path/path.rb +104 -0
- data/lib/rspec/json_matchers/utils.rb +10 -0
- data/lib/rspec/json_matchers/version.rb +8 -0
- data/lib/rspec/json_matchers.rb +15 -0
- data/lib/rspec-json_matchers.rb +1 -0
- data/rspec-json_matchers.gemspec +47 -0
- metadata +245 -0
@@ -0,0 +1,289 @@
|
|
1
|
+
require "abstract_class"
|
2
|
+
require "set"
|
3
|
+
require_relative "../expectation"
|
4
|
+
require_relative "comparison_result"
|
5
|
+
|
6
|
+
module RSpec
|
7
|
+
module JsonMatchers
|
8
|
+
module Comparers
|
9
|
+
# @api private
|
10
|
+
# @abstract
|
11
|
+
#
|
12
|
+
# The parent of all comparer classes
|
13
|
+
# It holds most of the responsibility
|
14
|
+
# The subclasses only need to implement the behaviour of matching keys
|
15
|
+
# when both expected & actual are same type of collection
|
16
|
+
class AbstractComparer
|
17
|
+
attr_reader *[
|
18
|
+
:actual,
|
19
|
+
:expected,
|
20
|
+
:reasons,
|
21
|
+
:value_matching_proc,
|
22
|
+
]
|
23
|
+
|
24
|
+
# Creates a comparer that actually use the {value_matching_proc} for matching {#actual} and {#expected}
|
25
|
+
# This class is respossible to aggregating
|
26
|
+
# the matching result for each element of {#expected},
|
27
|
+
# and compare the keys/indices as well
|
28
|
+
#
|
29
|
+
# @param actual [Object] the actual value
|
30
|
+
# @param expected [Object] the expected value
|
31
|
+
# @param reasons [Array<String>]
|
32
|
+
# failure reasons, mostly the path parts
|
33
|
+
# @param value_matching_proc [Proc]
|
34
|
+
# the proc that actually compares the expected & actual and returns a boolean
|
35
|
+
def initialize(actual, expected, reasons, value_matching_proc)
|
36
|
+
@actual = actual
|
37
|
+
@expected = expected
|
38
|
+
@reasons = reasons
|
39
|
+
|
40
|
+
@value_matching_proc = value_matching_proc
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Boolean]
|
44
|
+
# `true` if #actual & #expected are the same
|
45
|
+
def compare
|
46
|
+
if has_matched_value?
|
47
|
+
return ComparisonResult.new(true, reasons)
|
48
|
+
end
|
49
|
+
|
50
|
+
has_matched_collection?
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def has_matched_value?
|
56
|
+
value_matching_proc.call(expected, actual)
|
57
|
+
end
|
58
|
+
|
59
|
+
def has_matched_collection?
|
60
|
+
return ComparisonResult.new(false, reasons) unless is_collection?
|
61
|
+
return ComparisonResult.new(false, reasons) unless has_matched_class?
|
62
|
+
return ComparisonResult.new(false, reasons) unless has_matched_keys?
|
63
|
+
|
64
|
+
ComparisonResult.new(has_matched_values?, reasons)
|
65
|
+
end
|
66
|
+
|
67
|
+
def is_collection?
|
68
|
+
actual.is_a?(Array) || actual.is_a?(Hash)
|
69
|
+
end
|
70
|
+
|
71
|
+
def has_matched_class?
|
72
|
+
actual.class == expected.class
|
73
|
+
end
|
74
|
+
|
75
|
+
# @note with side effect on `#reasons`
|
76
|
+
def has_matched_keys?
|
77
|
+
raise NotImplementedError
|
78
|
+
end
|
79
|
+
|
80
|
+
# @note with side effect on `#reasons`
|
81
|
+
def has_matched_values?
|
82
|
+
comparison_result = {
|
83
|
+
Array => HasMatchedArrayValues,
|
84
|
+
Hash => HasMatchedHashValues,
|
85
|
+
}.fetch(expected.class).
|
86
|
+
new(expected, actual, reasons, value_matching_proc, self.class).
|
87
|
+
comparison_result
|
88
|
+
|
89
|
+
comparison_result.matched?.tap do |matched|
|
90
|
+
@reasons = comparison_result.reasons unless matched
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class HasMatchedValues
|
95
|
+
extend AbstractClass
|
96
|
+
|
97
|
+
private
|
98
|
+
attr_reader *[
|
99
|
+
:actual,
|
100
|
+
:expected,
|
101
|
+
:reasons,
|
102
|
+
:value_matching_proc,
|
103
|
+
|
104
|
+
:comparer_class,
|
105
|
+
]
|
106
|
+
public
|
107
|
+
|
108
|
+
# Create a "matching" operation object that can return a {Comparers::ComparisonResult}
|
109
|
+
#
|
110
|
+
# @param expected [Object] the expected "thing", should be an {Enumerable}
|
111
|
+
# @param actual [Object] the actual "thing", should be an {Enumerable}
|
112
|
+
# @param reasons [Array<String>]
|
113
|
+
# failure reasons, mostly the path parts
|
114
|
+
# @param value_matching_proc [Proc]
|
115
|
+
# the proc that actually compares the expected & actual and returns a boolean
|
116
|
+
# @param comparer_class [Class<AbstractComparer>]
|
117
|
+
# the class that should be used recursively
|
118
|
+
def initialize(expected, actual, reasons, value_matching_proc, comparer_class)
|
119
|
+
@actual = actual
|
120
|
+
@expected = expected
|
121
|
+
@reasons = reasons
|
122
|
+
|
123
|
+
@value_matching_proc = value_matching_proc
|
124
|
+
|
125
|
+
@comparer_class = comparer_class
|
126
|
+
end
|
127
|
+
|
128
|
+
def comparison_result
|
129
|
+
each_element_enumerator.each do |element|
|
130
|
+
comparison_result = has_matched_value_class.new(
|
131
|
+
element,
|
132
|
+
expected,
|
133
|
+
actual,
|
134
|
+
reasons,
|
135
|
+
value_matching_proc,
|
136
|
+
comparer_class,
|
137
|
+
).comparison_result
|
138
|
+
|
139
|
+
return comparison_result unless comparison_result.matched?
|
140
|
+
end
|
141
|
+
|
142
|
+
Comparers::ComparisonResult.new(true, reasons)
|
143
|
+
end
|
144
|
+
|
145
|
+
def each_element_enumerator
|
146
|
+
raise NotImplementedError
|
147
|
+
end
|
148
|
+
|
149
|
+
def has_matched_value_class
|
150
|
+
raise NotImplementedError
|
151
|
+
end
|
152
|
+
|
153
|
+
class HasMatchedValue
|
154
|
+
extend AbstractClass
|
155
|
+
|
156
|
+
private
|
157
|
+
attr_reader *[
|
158
|
+
:element,
|
159
|
+
|
160
|
+
:actual,
|
161
|
+
:expected,
|
162
|
+
:reasons,
|
163
|
+
|
164
|
+
:value_matching_proc,
|
165
|
+
|
166
|
+
:comparer_class,
|
167
|
+
]
|
168
|
+
public
|
169
|
+
|
170
|
+
# Create a "matching" operation object that can return a {Comparers::ComparisonResult}
|
171
|
+
# Unlike {HasMatchedValues}, this is for an element of `expected`
|
172
|
+
#
|
173
|
+
# @param element [Integer, String, Symbol] a index/key from expected (not value)
|
174
|
+
# @param (see HasMatchedValues#initialize)
|
175
|
+
def initialize(element, expected, actual, reasons, value_matching_proc, comparer_class)
|
176
|
+
@element = element
|
177
|
+
@actual = actual
|
178
|
+
@expected = expected
|
179
|
+
@reasons = reasons
|
180
|
+
|
181
|
+
@value_matching_proc = value_matching_proc
|
182
|
+
|
183
|
+
@comparer_class = comparer_class
|
184
|
+
end
|
185
|
+
|
186
|
+
def comparison_result
|
187
|
+
return false unless actual_contain_element?
|
188
|
+
|
189
|
+
result.tap do |result|
|
190
|
+
next if result.matched?
|
191
|
+
result.reasons.push(reason)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def result
|
198
|
+
@result ||= comparer_class.
|
199
|
+
new(
|
200
|
+
actual_for_element,
|
201
|
+
expected_for_element,
|
202
|
+
reasons,
|
203
|
+
value_matching_proc,
|
204
|
+
).
|
205
|
+
compare
|
206
|
+
end
|
207
|
+
|
208
|
+
def actual_contain_element?
|
209
|
+
raise NotImplementedError
|
210
|
+
end
|
211
|
+
|
212
|
+
def actual_for_element
|
213
|
+
raise NotImplementedError
|
214
|
+
end
|
215
|
+
def expected_for_element
|
216
|
+
raise NotImplementedError
|
217
|
+
end
|
218
|
+
|
219
|
+
def reason
|
220
|
+
raise NotImplementedError
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
class HasMatchedArrayValues < HasMatchedValues
|
226
|
+
def each_element_enumerator
|
227
|
+
expected.each_index
|
228
|
+
end
|
229
|
+
|
230
|
+
def has_matched_value_class
|
231
|
+
HasMatchedArrayValue
|
232
|
+
end
|
233
|
+
|
234
|
+
class HasMatchedArrayValue < HasMatchedValues::HasMatchedValue
|
235
|
+
private
|
236
|
+
alias_method :index, :element
|
237
|
+
public
|
238
|
+
|
239
|
+
def actual_contain_element?
|
240
|
+
index < actual.size
|
241
|
+
end
|
242
|
+
|
243
|
+
def actual_for_element
|
244
|
+
actual[index]
|
245
|
+
end
|
246
|
+
def expected_for_element
|
247
|
+
expected[index]
|
248
|
+
end
|
249
|
+
|
250
|
+
def reason
|
251
|
+
"[#{index}]"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
class HasMatchedHashValues < HasMatchedValues
|
257
|
+
def each_element_enumerator
|
258
|
+
expected.each_key
|
259
|
+
end
|
260
|
+
|
261
|
+
def has_matched_value_class
|
262
|
+
HasMatchedHashValue
|
263
|
+
end
|
264
|
+
|
265
|
+
class HasMatchedHashValue < HasMatchedValues::HasMatchedValue
|
266
|
+
private
|
267
|
+
alias_method :key, :element
|
268
|
+
public
|
269
|
+
|
270
|
+
def actual_contain_element?
|
271
|
+
actual.key?(key.to_s)
|
272
|
+
end
|
273
|
+
|
274
|
+
def actual_for_element
|
275
|
+
actual[key.to_s]
|
276
|
+
end
|
277
|
+
def expected_for_element
|
278
|
+
expected[key]
|
279
|
+
end
|
280
|
+
|
281
|
+
def reason
|
282
|
+
key
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RSpec
|
2
|
+
module JsonMatchers
|
3
|
+
module Comparers
|
4
|
+
# @api private
|
5
|
+
#
|
6
|
+
# A value object returned by comparers
|
7
|
+
# Instead of just Boolean
|
8
|
+
class ComparisonResult
|
9
|
+
attr_reader :reasons
|
10
|
+
|
11
|
+
def initialize(matched, reasons)
|
12
|
+
@matched = matched
|
13
|
+
@reasons = reasons
|
14
|
+
end
|
15
|
+
|
16
|
+
def matched?
|
17
|
+
!!@matched
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative "abstract_comparer"
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module JsonMatchers
|
5
|
+
module Comparers
|
6
|
+
# @api private
|
7
|
+
#
|
8
|
+
# The comparer class that disallow actual collection
|
9
|
+
# to have more properties/elements than expected collection
|
10
|
+
class ExactKeysComparer < AbstractComparer
|
11
|
+
private
|
12
|
+
|
13
|
+
# @note with side effect on `#reasons`
|
14
|
+
def has_matched_keys?
|
15
|
+
actual_keys = Utils::CollectionKeysExtractor.extract(actual)
|
16
|
+
expected_keys = Utils::CollectionKeysExtractor.extract(expected)
|
17
|
+
(actual_keys == expected_keys).tap do |success|
|
18
|
+
unless success
|
19
|
+
diff_keys = (actual_keys - expected_keys) + (expected_keys - actual_keys)
|
20
|
+
reasons.push(diff_keys.awesome_inspect)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative "abstract_comparer"
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module JsonMatchers
|
5
|
+
module Comparers
|
6
|
+
# @api private
|
7
|
+
#
|
8
|
+
# The comparer class that allow actual collection
|
9
|
+
# to have more properties/elements than expected collection
|
10
|
+
class IncludeKeysComparer < AbstractComparer
|
11
|
+
private
|
12
|
+
|
13
|
+
# @note with side effect on `#reasons`
|
14
|
+
def has_matched_keys?
|
15
|
+
actual_keys = Utils::CollectionKeysExtractor.extract(actual)
|
16
|
+
expected_keys = Utils::CollectionKeysExtractor.extract(expected)
|
17
|
+
(expected_keys.subset?(actual_keys)).tap do |success|
|
18
|
+
unless success
|
19
|
+
diff_keys = (expected_keys - actual_keys)
|
20
|
+
reasons.push(diff_keys.awesome_inspect)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative "comparers/exact_keys_comparer"
|
2
|
+
require_relative "comparers/include_keys_comparer"
|
3
|
+
|
4
|
+
module RSpec
|
5
|
+
module JsonMatchers
|
6
|
+
# @api private
|
7
|
+
#
|
8
|
+
# All classes & modules here (including itself) are for internal use only
|
9
|
+
# They could be renamed or re-structured anytime
|
10
|
+
module Comparers
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "abstract_class"
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module JsonMatchers
|
5
|
+
# Represents an expectation of an object (usually called `expected`)
|
6
|
+
# Built to avoid {Object#===} usage like other matcher gems, like `rspec-json_matcher`
|
7
|
+
# Actually `rspec-mocks` `3.x` also uses it, but only internally
|
8
|
+
#
|
9
|
+
# @api
|
10
|
+
# This class can be extended to create custom kinds of expectation
|
11
|
+
# But only used for this gem
|
12
|
+
# @abstract
|
13
|
+
# This class MUST be used after being inherited
|
14
|
+
# Subclasses MUST override {#expect?} to allow this gem to determine the test result
|
15
|
+
class Expectation
|
16
|
+
extend AbstractClass
|
17
|
+
|
18
|
+
# @abstract
|
19
|
+
# This method MUST be overridden to allow this gem to determine the test result
|
20
|
+
#
|
21
|
+
# @param value [Object] actual value to be evaluated
|
22
|
+
#
|
23
|
+
# @return [Bool] Whether the `value` is expected
|
24
|
+
def expect?(value)
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
# @api private
|
30
|
+
#
|
31
|
+
# "Build" an expectation object (not class) depends on `value`
|
32
|
+
#
|
33
|
+
# @param value [Object]
|
34
|
+
# expected value, could be an expectation object as well
|
35
|
+
#
|
36
|
+
# @return [Expectation]
|
37
|
+
def build(value)
|
38
|
+
return value if value.is_a?(self)
|
39
|
+
|
40
|
+
if value.is_a?(Regexp)
|
41
|
+
return Expectations::Private::MatchingRegexp[value]
|
42
|
+
end
|
43
|
+
|
44
|
+
if value.is_a?(Range)
|
45
|
+
return Expectations::Private::InRange[value]
|
46
|
+
end
|
47
|
+
|
48
|
+
if value.respond_to?(:call)
|
49
|
+
return Expectations::Private::SatisfyingCallable[value]
|
50
|
+
end
|
51
|
+
|
52
|
+
if value.is_a?(Class)
|
53
|
+
# Subclass
|
54
|
+
# See http://ruby-doc.org/core-2.2.2/Module.html#method-i-3C
|
55
|
+
if value < Expectations::Core::SingletonExpectation
|
56
|
+
return value::INSTANCE
|
57
|
+
end
|
58
|
+
return Expectations::Private::KindOf[value]
|
59
|
+
end
|
60
|
+
|
61
|
+
Expectations::Private::Eq[value]
|
62
|
+
end
|
63
|
+
# @api private
|
64
|
+
#
|
65
|
+
# "Build" expectation objects (not classes) depending on `values`
|
66
|
+
#
|
67
|
+
# @return [Array<Expectation>]
|
68
|
+
# @see .build
|
69
|
+
def build_many(values)
|
70
|
+
values.flat_map{|v| build(v) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Classes in the following file(s) are required at runtime not parse time
|
78
|
+
require_relative "expectations"
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "abstract_class"
|
2
|
+
|
3
|
+
require_relative "core"
|
4
|
+
|
5
|
+
module RSpec
|
6
|
+
module JsonMatchers
|
7
|
+
module Expectations
|
8
|
+
# @api private
|
9
|
+
# All classes within module should NOT be able to be used directly / extended
|
10
|
+
#
|
11
|
+
# Classes in this namespace are depending on {Core}
|
12
|
+
# and depended by some classes in {Expectations::Mixins::BuiltIn}
|
13
|
+
# They are all abstract too, thus the naming, but might change
|
14
|
+
# This namespace is created is to avoid require order problem when putting classes here in {Private}
|
15
|
+
module Abstract
|
16
|
+
# @abstract
|
17
|
+
# Only for reducing code duplication
|
18
|
+
#
|
19
|
+
# Verifies the value passed in is a {Numeric}
|
20
|
+
#
|
21
|
+
# @note
|
22
|
+
# {Numeric} might not be the best class to check for
|
23
|
+
# Since not all subclasses of it are expected
|
24
|
+
# But for simplicity's sake this is used until problem raised
|
25
|
+
class NumericExpectation < Expectations::Core::SingletonExpectation
|
26
|
+
extend AbstractClass
|
27
|
+
|
28
|
+
def expect?(value)
|
29
|
+
value.is_a?(Numeric)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require "abstract_class"
|
2
|
+
|
3
|
+
require_relative "../expectation"
|
4
|
+
|
5
|
+
module RSpec
|
6
|
+
module JsonMatchers
|
7
|
+
module Expectations
|
8
|
+
# @api
|
9
|
+
# All classes within module should be able to be used / extended
|
10
|
+
# Unless specified otherwise
|
11
|
+
#
|
12
|
+
# All public expectation classes that can be extended
|
13
|
+
# even by classes in extension gems
|
14
|
+
module Core
|
15
|
+
# @abstract
|
16
|
+
# This class MUST be used after being inherited
|
17
|
+
# Subclasses will have a constant `INSTANCE` storing the only instance of that class
|
18
|
+
# @note
|
19
|
+
# This class assumed descendants to NOT override {.inherited} or call `super` if overridden
|
20
|
+
# Otherwise the constant `INSTANCE` won't work
|
21
|
+
# @note
|
22
|
+
# The constant `INSTANCE` will be referred with namespace,
|
23
|
+
# which eliminates the possibility of using parent class constant
|
24
|
+
# @see
|
25
|
+
# https://stackoverflow.com/questions/3174563/how-to-use-an-overridden-constant-in-an-inheritanced-class
|
26
|
+
# @note
|
27
|
+
# Pattern comes from gem rspec-mocks
|
28
|
+
# @see
|
29
|
+
# https://github.com/rspec/rspec-mocks/blob/3-2-maintenance/lib/rspec/mocks/argument_matchers.rb
|
30
|
+
class SingletonExpectation < Expectation
|
31
|
+
extend AbstractClass
|
32
|
+
|
33
|
+
private_class_method :new
|
34
|
+
|
35
|
+
def self.inherited(subclass)
|
36
|
+
subclass.const_set(:INSTANCE, subclass.send(:new))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Allow class to be called with `.[]` instead of `.new`
|
41
|
+
#
|
42
|
+
# @abstract
|
43
|
+
# This class MUST be used after being inherited
|
44
|
+
class CallableExpectation < Expectation
|
45
|
+
extend AbstractClass
|
46
|
+
|
47
|
+
# The replacement of {.new}
|
48
|
+
# It accept any number of arguments and delegates to private {.new}
|
49
|
+
# This pattern is taken from gem `contracts`
|
50
|
+
#
|
51
|
+
# @see https://github.com/egonSchiele/contracts.ruby
|
52
|
+
#
|
53
|
+
# @return [Expectation] an expectation object
|
54
|
+
def self.[](*values)
|
55
|
+
new(*values)
|
56
|
+
end
|
57
|
+
|
58
|
+
private_class_method :new
|
59
|
+
end
|
60
|
+
|
61
|
+
# Validates exactly one value is passed in
|
62
|
+
#
|
63
|
+
# @abstract
|
64
|
+
class SingleValueCallableExpectation < CallableExpectation
|
65
|
+
EXPECTED_VALUE_SIZE = 1
|
66
|
+
private_constant :EXPECTED_VALUE_SIZE
|
67
|
+
|
68
|
+
# (see CallableExpectation.[])
|
69
|
+
# But only 1 argument is accepted
|
70
|
+
def self.[](*values)
|
71
|
+
unless values.size == EXPECTED_VALUE_SIZE
|
72
|
+
raise ArgumentError, "Exactly #{EXPECTED_VALUE_SIZE} argument is required"
|
73
|
+
end
|
74
|
+
super
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Takes any number of objects and converts into expectation objects (if not already)
|
79
|
+
#
|
80
|
+
# @abstract
|
81
|
+
class CompositeExpectation < CallableExpectation
|
82
|
+
extend AbstractClass
|
83
|
+
|
84
|
+
private
|
85
|
+
attr_reader :expectations
|
86
|
+
public
|
87
|
+
|
88
|
+
# (see CallableExpectation.[])
|
89
|
+
# Also all values will be converted into expectations
|
90
|
+
def self.[](*values)
|
91
|
+
super(build_many(values))
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def initialize(expectations)
|
97
|
+
@expectations = expectations
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|