matchers 0.1.0.pre.test
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.
Potentially problematic release.
This version of matchers might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/lib/matcher/assertions.rb +21 -0
- data/lib/matcher/base.rb +189 -0
- data/lib/matcher/builder.rb +74 -0
- data/lib/matcher/chain.rb +60 -0
- data/lib/matcher/debug.rb +48 -0
- data/lib/matcher/errors/and_error.rb +86 -0
- data/lib/matcher/errors/boolean_collector.rb +51 -0
- data/lib/matcher/errors/element_error.rb +24 -0
- data/lib/matcher/errors/empty_error.rb +23 -0
- data/lib/matcher/errors/error.rb +39 -0
- data/lib/matcher/errors/error_collector.rb +99 -0
- data/lib/matcher/errors/nested_error.rb +96 -0
- data/lib/matcher/errors/or_error.rb +86 -0
- data/lib/matcher/expression_cache.rb +57 -0
- data/lib/matcher/expression_labeler.rb +91 -0
- data/lib/matcher/expressions/array_expression.rb +45 -0
- data/lib/matcher/expressions/block.rb +153 -0
- data/lib/matcher/expressions/call.rb +338 -0
- data/lib/matcher/expressions/call_error.rb +45 -0
- data/lib/matcher/expressions/constant.rb +53 -0
- data/lib/matcher/expressions/expression.rb +147 -0
- data/lib/matcher/expressions/expression_building.rb +258 -0
- data/lib/matcher/expressions/expression_walker.rb +73 -0
- data/lib/matcher/expressions/hash_expression.rb +59 -0
- data/lib/matcher/expressions/proc_expression.rb +92 -0
- data/lib/matcher/expressions/range_expression.rb +58 -0
- data/lib/matcher/expressions/recorder.rb +86 -0
- data/lib/matcher/expressions/rescue_last_error_expression.rb +44 -0
- data/lib/matcher/expressions/set_expression.rb +45 -0
- data/lib/matcher/expressions/string_expression.rb +53 -0
- data/lib/matcher/expressions/symbol_proc.rb +53 -0
- data/lib/matcher/expressions/variable.rb +85 -0
- data/lib/matcher/hash_stack.rb +53 -0
- data/lib/matcher/list.rb +102 -0
- data/lib/matcher/markers/optional.rb +80 -0
- data/lib/matcher/markers/others.rb +28 -0
- data/lib/matcher/matcher_cache.rb +18 -0
- data/lib/matcher/matchers/all_matcher.rb +60 -0
- data/lib/matcher/matchers/always_matcher.rb +28 -0
- data/lib/matcher/matchers/any_matcher.rb +70 -0
- data/lib/matcher/matchers/array_matcher.rb +35 -0
- data/lib/matcher/matchers/block_matcher.rb +59 -0
- data/lib/matcher/matchers/boolean_matcher.rb +35 -0
- data/lib/matcher/matchers/dig_matcher.rb +146 -0
- data/lib/matcher/matchers/each_matcher.rb +52 -0
- data/lib/matcher/matchers/each_pair_matcher.rb +119 -0
- data/lib/matcher/matchers/equal_matcher.rb +197 -0
- data/lib/matcher/matchers/equal_set_matcher.rb +99 -0
- data/lib/matcher/matchers/expression_matcher.rb +73 -0
- data/lib/matcher/matchers/filter_matcher.rb +111 -0
- data/lib/matcher/matchers/hash_matcher.rb +223 -0
- data/lib/matcher/matchers/imply_matcher.rb +81 -0
- data/lib/matcher/matchers/imply_some_matcher.rb +112 -0
- data/lib/matcher/matchers/index_by_matcher.rb +175 -0
- data/lib/matcher/matchers/inline_matcher.rb +99 -0
- data/lib/matcher/matchers/keys_matcher.rb +121 -0
- data/lib/matcher/matchers/kind_of_matcher.rb +35 -0
- data/lib/matcher/matchers/lazy_all_matcher.rb +68 -0
- data/lib/matcher/matchers/lazy_any_matcher.rb +68 -0
- data/lib/matcher/matchers/let_matcher.rb +73 -0
- data/lib/matcher/matchers/map_matcher.rb +129 -0
- data/lib/matcher/matchers/matcher_building.rb +5 -0
- data/lib/matcher/matchers/negated_array_matcher.rb +38 -0
- data/lib/matcher/matchers/negated_each_matcher.rb +36 -0
- data/lib/matcher/matchers/negated_each_pair_matcher.rb +38 -0
- data/lib/matcher/matchers/negated_imply_some_matcher.rb +46 -0
- data/lib/matcher/matchers/negated_matcher.rb +23 -0
- data/lib/matcher/matchers/negated_project_matcher.rb +31 -0
- data/lib/matcher/matchers/never_matcher.rb +29 -0
- data/lib/matcher/matchers/one_matcher.rb +70 -0
- data/lib/matcher/matchers/optional_matcher.rb +38 -0
- data/lib/matcher/matchers/parse_float_matcher.rb +86 -0
- data/lib/matcher/matchers/parse_integer_matcher.rb +98 -0
- data/lib/matcher/matchers/parse_iso8601_matcher.rb +92 -0
- data/lib/matcher/matchers/parse_json_matcher.rb +95 -0
- data/lib/matcher/matchers/project_matcher.rb +68 -0
- data/lib/matcher/matchers/raises_matcher.rb +124 -0
- data/lib/matcher/matchers/range_matcher.rb +47 -0
- data/lib/matcher/matchers/reference_matcher.rb +111 -0
- data/lib/matcher/matchers/reference_matcher_collection.rb +57 -0
- data/lib/matcher/matchers/regexp_matcher.rb +84 -0
- data/lib/matcher/messages/expected_phrasing.rb +342 -0
- data/lib/matcher/messages/message.rb +102 -0
- data/lib/matcher/messages/message_builder.rb +35 -0
- data/lib/matcher/messages/message_rules.rb +223 -0
- data/lib/matcher/messages/namespaced_message_builder.rb +19 -0
- data/lib/matcher/messages/phrasing.rb +57 -0
- data/lib/matcher/messages/standard_message_builder.rb +105 -0
- data/lib/matcher/once_before.rb +18 -0
- data/lib/matcher/optional_chain.rb +24 -0
- data/lib/matcher/patterns/ast_mapping.rb +42 -0
- data/lib/matcher/patterns/capture_hole.rb +33 -0
- data/lib/matcher/patterns/constant_hole.rb +14 -0
- data/lib/matcher/patterns/hole.rb +30 -0
- data/lib/matcher/patterns/method_hole.rb +58 -0
- data/lib/matcher/patterns/pattern.rb +92 -0
- data/lib/matcher/patterns/pattern_building.rb +39 -0
- data/lib/matcher/patterns/pattern_capture.rb +11 -0
- data/lib/matcher/patterns/pattern_match.rb +29 -0
- data/lib/matcher/patterns/variable_hole.rb +14 -0
- data/lib/matcher/reporter.rb +98 -0
- data/lib/matcher/rules/message_factory.rb +25 -0
- data/lib/matcher/rules/message_rule.rb +18 -0
- data/lib/matcher/rules/message_rule_context.rb +24 -0
- data/lib/matcher/rules/rule_builder.rb +29 -0
- data/lib/matcher/rules/rule_set.rb +57 -0
- data/lib/matcher/rules/transform_builder.rb +24 -0
- data/lib/matcher/rules/transform_mapping.rb +5 -0
- data/lib/matcher/rules/transform_rule.rb +21 -0
- data/lib/matcher/state.rb +40 -0
- data/lib/matcher/testing/error_builder.rb +62 -0
- data/lib/matcher/testing/error_checker.rb +496 -0
- data/lib/matcher/testing/error_testing.rb +37 -0
- data/lib/matcher/testing/pattern_testing.rb +11 -0
- data/lib/matcher/testing/pattern_testing_scope.rb +34 -0
- data/lib/matcher/testing.rb +102 -0
- data/lib/matcher/undefined.rb +10 -0
- data/lib/matcher/utils/mapping_utils.rb +61 -0
- data/lib/matcher/utils.rb +72 -0
- data/lib/matcher/version.rb +5 -0
- data/lib/matcher.rb +337 -0
- metadata +167 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class ExpectedPhrasing < Phrasing
|
|
5
|
+
define(:truthy) do
|
|
6
|
+
verb = verb(negated: true)
|
|
7
|
+
truthy_or_falsy = negated ? 'truthy' : 'falsy'
|
|
8
|
+
|
|
9
|
+
"#{verb} a #{truthy_or_falsy} value but got #{actual.inspect}"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
define(:same) do |object|
|
|
13
|
+
"#{verb} same as #{object.inspect} (id=#{object.object_id})" \
|
|
14
|
+
"#{" but got #{actual.inspect} (id=#{actual.object_id})" if negated}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
define(:equal) do |value|
|
|
18
|
+
if negated
|
|
19
|
+
"#{verb} #{value.inspect} but got #{actual.inspect}"
|
|
20
|
+
else
|
|
21
|
+
"#{verb} #{actual.inspect}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
define(:less_than) do |operand|
|
|
26
|
+
if negated || (actual <=> operand).nil?
|
|
27
|
+
"#{verb} a value < #{operand.inspect} but got #{actual.inspect}"
|
|
28
|
+
else
|
|
29
|
+
phrase_negated(:greater_than_or_equal, operand)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
define(:greater_than) do |operand|
|
|
34
|
+
if negated || (actual <=> operand).nil?
|
|
35
|
+
"#{verb} a value > #{operand.inspect} but got #{actual.inspect}"
|
|
36
|
+
else
|
|
37
|
+
phrase_negated(:less_than_or_equal, operand)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
define(:less_than_or_equal) do |operand|
|
|
42
|
+
if negated || (actual <=> operand).nil?
|
|
43
|
+
"#{verb} a value <= #{operand.inspect} but got #{actual.inspect}"
|
|
44
|
+
else
|
|
45
|
+
phrase_negated(:greater_than, operand)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
define(:greater_than_or_equal) do |operand|
|
|
50
|
+
if negated || (actual <=> operand).nil?
|
|
51
|
+
"#{verb} a value >= #{operand.inspect} but got #{actual.inspect}"
|
|
52
|
+
else
|
|
53
|
+
phrase_negated(:less_than, operand)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
define(:comparable_to) do |operand|
|
|
58
|
+
"#{verb} a value comparable to #{operand.inspect} but got #{actual.inspect}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
define(:between) do |min, max, exclude_end: false|
|
|
62
|
+
"#{verb} value to be between #{min.inspect} and " \
|
|
63
|
+
"#{max.inspect}#{' (exclusive)' if exclude_end} but got #{actual.inspect}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
define(:length_of) do |exp, act|
|
|
67
|
+
"#{verb} length of #{exp}#{" but was #{act}" if negated}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
define(:having_key) do |key|
|
|
71
|
+
"#{verb} to include key #{key.inspect} but got #{actual.inspect}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
define(:having_index) do |index|
|
|
75
|
+
"#{verb} to have index #{index.inspect} but got #{actual.inspect}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
define(:exist) do
|
|
79
|
+
"#{verb} to exist#{" but got #{actual.inspect}" unless negated}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
define(:in) do |collection|
|
|
83
|
+
"#{verb} object to be included in #{collection.inspect} but got #{actual.inspect}"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
define(:including) do |item|
|
|
87
|
+
"#{verb} #{item.inspect} to be included but got #{actual.inspect}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
define(:duplicate) do |original_index|
|
|
91
|
+
"#{verb} duplicate originally at index #{original_index} but got #{actual.inspect}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
define(:duplicate_by) do |expression, value, original_index|
|
|
95
|
+
"#{verb} duplicate by #{expression.inspect}=#{value.inspect} originally at index #{original_index} but got #{actual.inspect}"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
define(:matching) do |pattern|
|
|
99
|
+
"#{verb} value to match #{pattern.inspect} but got #{actual.inspect}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
define(:valid_format) do |format|
|
|
103
|
+
"#{verb} a valid #{format} string but got #{actual.inspect}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
define(:instance_of) do |klass|
|
|
107
|
+
"#{verb} an instance of #{klass} but got #{actual.inspect}"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
define(:kind_of) do |klass|
|
|
111
|
+
"#{verb} a kind of #{klass} but got #{actual.inspect}"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
define(:responding_to) do |method|
|
|
115
|
+
"#{verb} an object responding to `#{method}' but got #{actual.inspect}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
define(:predicate) do |predicate|
|
|
119
|
+
predicate = predicate.to_s.delete_suffix('?')
|
|
120
|
+
|
|
121
|
+
"#{verb} value to be #{predicate} but got #{actual.inspect}"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
define(:described_by) do |description|
|
|
125
|
+
"#{verb} #{description} but got #{actual.inspect}"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
namespace(:expression) do
|
|
129
|
+
define(:truthy) do |expression, value, given|
|
|
130
|
+
verb = verb(negated: true)
|
|
131
|
+
truthy_or_falsy = negated ? 'truthy' : 'falsy'
|
|
132
|
+
|
|
133
|
+
"#{verb} #{expression} to be #{truthy_or_falsy} " \
|
|
134
|
+
"but got #{value.inspect}#{where_text(given, expression)}"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
define(:same) do |left_expression, right_expression, left, right, given|
|
|
138
|
+
"#{verb} #{left_expression} to be same as #{right_expression} " \
|
|
139
|
+
"but got #{left.inspect} (id=#{left.object_id})" \
|
|
140
|
+
"#{" and #{right.inspect} (id=#{right.object_id})" if negated}" \
|
|
141
|
+
"#{where_text(given, left_expression, right_expression)}"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
negated_comparisons =
|
|
145
|
+
{ :== => :!=, :!= => :==, :< => :>=, :> => :<=, :<= => :>, :>= => :< }
|
|
146
|
+
|
|
147
|
+
define(:comparison) do |expression, left, right, given|
|
|
148
|
+
can_negate = !negated &&
|
|
149
|
+
(method = negated_comparisons[expression.method]) &&
|
|
150
|
+
(%i[== !=].include?(method) || left <=> right)
|
|
151
|
+
|
|
152
|
+
verb = if can_negate
|
|
153
|
+
expression = Call.new(
|
|
154
|
+
expression.receiver,
|
|
155
|
+
method,
|
|
156
|
+
expression.args,
|
|
157
|
+
expression.kwargs,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
verb(negated: true)
|
|
161
|
+
else
|
|
162
|
+
self.verb
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
"#{verb} #{expression} but got " \
|
|
166
|
+
"#{left.inspect} #{expression.method} #{right.inspect}" \
|
|
167
|
+
"#{where_text(given, expression.receiver, expression.args[0])}"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
define(:comparable_to) do |expression, value, operand, given|
|
|
171
|
+
"#{verb} #{expression} to be comparable to #{operand.inspect} " \
|
|
172
|
+
"but got #{value.inspect}#{where_text(given, expression)}"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
define(:between) do |expression, value, min, max, given|
|
|
176
|
+
"#{verb} #{expression} to be between #{min.inspect} and #{max.inspect} " \
|
|
177
|
+
"but got #{value.inspect}#{where_text(given, expression)}"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
define(:length_of) do |expression, _value, exp, act, given|
|
|
181
|
+
"#{verb} #{expression} to have length of #{exp}" \
|
|
182
|
+
"#{" but was #{act}" if negated}#{where_text(given, expression)}"
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
define(:having_key) do |expression, value, key, given|
|
|
186
|
+
"#{verb} #{expression} to include key #{key.inspect} " \
|
|
187
|
+
"but got #{value.inspect}#{where_text(given, expression)}"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
define(:in) do |expression, value, collection, given|
|
|
191
|
+
"#{verb} #{expression} to be included in #{collection.inspect} " \
|
|
192
|
+
"but got #{value.inspect}#{where_text(given, expression)}"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
define(:including) do |expression, value, item, given|
|
|
196
|
+
"#{verb} #{expression} to include #{item.inspect} " \
|
|
197
|
+
"but got #{value.inspect}#{where_text(given, expression)}"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
define(:matching) do |expression, value, pattern, given|
|
|
201
|
+
"#{verb} #{expression} to match #{pattern.inspect} " \
|
|
202
|
+
"but got #{value.inspect}#{where_text(given, expression)}"
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
match_at_words = {
|
|
206
|
+
:== => 'at',
|
|
207
|
+
:!= => 'not at',
|
|
208
|
+
:< => 'before',
|
|
209
|
+
:> => 'after',
|
|
210
|
+
:<= => 'at or before',
|
|
211
|
+
:>= => 'at or after',
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
define(:match_at) do |expression, value, pattern, position, comparison, operand, given|
|
|
215
|
+
comparison_word = match_at_words[comparison]
|
|
216
|
+
|
|
217
|
+
message = String.new
|
|
218
|
+
message << "#{verb} #{expression} to match #{pattern.inspect} #{comparison_word} #{operand} "
|
|
219
|
+
message << "but was at #{position} " if
|
|
220
|
+
operand != position || comparison != (negated ? :!= : :==)
|
|
221
|
+
message << "for #{value.inspect}#{where_text(given, expression)}"
|
|
222
|
+
|
|
223
|
+
message
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
define(:instance_of) do |expression, value, klass, given|
|
|
227
|
+
"#{verb} #{expression} to be an instance of #{klass} " \
|
|
228
|
+
"but got #{value.inspect}#{where_text(given, expression)}"
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
define(:kind_of) do |expression, value, klass, given|
|
|
232
|
+
"#{verb} #{expression} to be a kind of #{klass} " \
|
|
233
|
+
"but got #{value.inspect}#{where_text(given, expression)}"
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
define(:responding_to) do |expression, value, method, given|
|
|
237
|
+
"#{verb} #{expression} to respond to `#{method}' " \
|
|
238
|
+
"but got #{value.inspect}#{where_text(given, expression)}"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
define(:predicate) do |expression, value, predicate, given|
|
|
242
|
+
predicate = predicate.to_s.delete_suffix('?')
|
|
243
|
+
|
|
244
|
+
"#{verb} #{expression} to be #{predicate} " \
|
|
245
|
+
"but got #{value.inspect}#{where_text(given, expression)}"
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
define(:raising) do |expression, error, given|
|
|
249
|
+
error_class = error.is_a?(Class) ? error : error.class
|
|
250
|
+
where = where_text(given, expression)
|
|
251
|
+
|
|
252
|
+
text = "#{verb} #{expression} to raise #{error_class}#{where}"
|
|
253
|
+
text += ": #{error.message}" if error.is_a?(Exception)
|
|
254
|
+
|
|
255
|
+
text
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
namespace(:negated) do
|
|
260
|
+
define(:valid) do |matcher|
|
|
261
|
+
"#{verb} #{matcher} to be valid but got #{actual.inspect}"
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
namespace(:block) do
|
|
266
|
+
define(:satisfied) do |block_location|
|
|
267
|
+
"#{verb} to satisfy condition #{block_location} but got #{actual.inspect}"
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
namespace(:imply_some) do
|
|
272
|
+
def x_conditions(count)
|
|
273
|
+
case count
|
|
274
|
+
when :any
|
|
275
|
+
'any condition'
|
|
276
|
+
when 1
|
|
277
|
+
'one condition'
|
|
278
|
+
else
|
|
279
|
+
"#{count} conditions"
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
private :x_conditions
|
|
283
|
+
|
|
284
|
+
define(:no_condition_satisfied) do |conditions, count|
|
|
285
|
+
"#{negated_verb} to satisfy #{x_conditions(count)} but got #{actual.inspect} and met none of these: #{join(conditions)}"
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
define(:x_conditions_satisfied) do |conditions, count|
|
|
289
|
+
"#{negated_verb} to satisfy #{x_conditions(count)} but got #{actual.inspect} and met these: #{join(conditions)}"
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
namespace(:reference) do
|
|
294
|
+
define(:cyclic) do
|
|
295
|
+
"#{verb} a cyclic structure" \
|
|
296
|
+
"#{' but actual has already been visited' unless negated}"
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
define(:failed_from_cache) do
|
|
300
|
+
'actual has already failed before'
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
namespace(:set) do
|
|
305
|
+
define(:equal) do |set|
|
|
306
|
+
"#{verb} object to be an equal set to #{set.inspect} but got #{actual.inspect}"
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
private
|
|
311
|
+
|
|
312
|
+
def verb(negated: self.negated)
|
|
313
|
+
# A message says what actual is but expected says what it is not. That's why
|
|
314
|
+
# the verb is counter-intuitively negated.
|
|
315
|
+
|
|
316
|
+
negated ? 'expected' : 'did not expect'
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def negated_verb
|
|
320
|
+
verb(negated: !negated)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def join(objects)
|
|
324
|
+
objects.map(&:to_s).join(', ')
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def where_text(values, *expressions)
|
|
328
|
+
return '' if values.empty?
|
|
329
|
+
|
|
330
|
+
except = expressions.filter_map { _1.symbol if _1.is_a?(Variable) }
|
|
331
|
+
symbols = expressions.flat_map(&:variables).uniq - except
|
|
332
|
+
|
|
333
|
+
return '' if symbols.empty?
|
|
334
|
+
|
|
335
|
+
list = symbols
|
|
336
|
+
.map { "#{_1} = #{values.fetch(_1).inspect}" }
|
|
337
|
+
.join(', ')
|
|
338
|
+
|
|
339
|
+
", where #{list}"
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class Message
|
|
5
|
+
attr_reader :key, :negated, :actual, :args, :kwargs
|
|
6
|
+
|
|
7
|
+
def initialize(key, negated, actual, *args, **kwargs)
|
|
8
|
+
@key = key
|
|
9
|
+
@negated = negated
|
|
10
|
+
@actual = actual
|
|
11
|
+
@args = args
|
|
12
|
+
@kwargs = kwargs
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def negate!
|
|
16
|
+
@negated = !@negated
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def ==(other)
|
|
20
|
+
return true if equal?(other)
|
|
21
|
+
|
|
22
|
+
other.instance_of?(Message) &&
|
|
23
|
+
@key.eql?(other.key) &&
|
|
24
|
+
@negated == other.negated &&
|
|
25
|
+
@actual.eql?(other.actual) &&
|
|
26
|
+
@args.eql?(other.args) &&
|
|
27
|
+
@kwargs.eql?(other.kwargs)
|
|
28
|
+
end
|
|
29
|
+
alias eql? ==
|
|
30
|
+
|
|
31
|
+
def hash
|
|
32
|
+
@hash ||= [self.class, @key, @negated, @actual, @args, @kwargs].hash
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def namespace
|
|
36
|
+
@key.is_a?(Array) ? @key[0] : nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_standard
|
|
40
|
+
return self if namespace != :expression
|
|
41
|
+
|
|
42
|
+
key = @key[1]
|
|
43
|
+
|
|
44
|
+
case key
|
|
45
|
+
when :truthy
|
|
46
|
+
_expr, value = @args
|
|
47
|
+
Message.new(key, @negated, value)
|
|
48
|
+
when :same
|
|
49
|
+
_l_expr, _r_expr, left, right = @args
|
|
50
|
+
Message.new(:same, @negated, left, right)
|
|
51
|
+
when :comparable_to, :having_key, :in, :including, :matching, :instance_of, :kind_of, :responding_to, :predicate
|
|
52
|
+
_expr, value, operand = @args
|
|
53
|
+
Message.new(key, @negated, value, operand)
|
|
54
|
+
when :between, :length_of
|
|
55
|
+
_expr, value, operand1, operand2 = @args
|
|
56
|
+
Message.new(key, @negated, value, operand1, operand2)
|
|
57
|
+
when :comparison
|
|
58
|
+
expr, left, right = @args
|
|
59
|
+
|
|
60
|
+
case expr.method
|
|
61
|
+
when :==
|
|
62
|
+
Message.new(:equal, @negated, left, right)
|
|
63
|
+
when :!=
|
|
64
|
+
Message.new(:equal, !@negated, left, right)
|
|
65
|
+
when :<
|
|
66
|
+
Message.new(:less_than, @negated, left, right)
|
|
67
|
+
when :>
|
|
68
|
+
Message.new(:greater_than, @negated, left, right)
|
|
69
|
+
when :<=
|
|
70
|
+
Message.new(:less_than_or_equal, @negated, left, right)
|
|
71
|
+
when :>=
|
|
72
|
+
Message.new(:greater_than_or_equal, @negated, left, right)
|
|
73
|
+
else
|
|
74
|
+
raise "unexpected method: #{expr.method.inspect}"
|
|
75
|
+
end
|
|
76
|
+
else
|
|
77
|
+
self
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def to_s
|
|
82
|
+
if @key.is_a?(Array)
|
|
83
|
+
namespace, key = @key
|
|
84
|
+
else
|
|
85
|
+
key = @key
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
args_and_kwargs = @args.map(&:inspect) + @kwargs.map do |k, v|
|
|
89
|
+
k.is_a?(Symbol) ? "#{k}: #{v.inspect}" : "#{k.inspect} => #{v.inspect}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
string = String.new("report(#{@actual.inspect})")
|
|
93
|
+
string += ".namespace(#{namespace.inspect})" if namespace
|
|
94
|
+
string += '.not' if @negated
|
|
95
|
+
string += ".#{key}"
|
|
96
|
+
string += "(#{args_and_kwargs.join(', ')})" unless args_and_kwargs.empty?
|
|
97
|
+
|
|
98
|
+
string
|
|
99
|
+
end
|
|
100
|
+
alias inspect to_s
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class MessageBuilder
|
|
5
|
+
def initialize(negated, actual)
|
|
6
|
+
@negated = negated
|
|
7
|
+
@actual = actual
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def not
|
|
11
|
+
@negated = !@negated
|
|
12
|
+
self
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def not_if(condition)
|
|
16
|
+
condition ? self.not : self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
protected
|
|
20
|
+
|
|
21
|
+
def message(key, *, **)
|
|
22
|
+
Message.new(key, @negated, @actual, *, **)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def method_missing(method, *, **)
|
|
28
|
+
message(method, *, **)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def respond_to_missing?(_name, _include_private = false)
|
|
32
|
+
true
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
ExpressionMatcher.message_rules.configure do
|
|
5
|
+
# binary standard expression
|
|
6
|
+
standard_ops = %i[== != < > <= >= <=> =~ !~ equal? is_a? kind_of? instance_of? respond_to? key? include? in?]
|
|
7
|
+
message method_hole(:call, _, standard_ops, const(:operand)) do |v, e|
|
|
8
|
+
case e[:call].method
|
|
9
|
+
when :<
|
|
10
|
+
standard_message.less_than(v[:operand])
|
|
11
|
+
when :>
|
|
12
|
+
standard_message.greater_than(v[:operand])
|
|
13
|
+
when :<=
|
|
14
|
+
standard_message.less_than_or_equal(v[:operand])
|
|
15
|
+
when :>=
|
|
16
|
+
standard_message.greater_than_or_equal(v[:operand])
|
|
17
|
+
when :<=>
|
|
18
|
+
# <=> returns nil if operands are not comparable
|
|
19
|
+
standard_message.comparable_to(v[:operand])
|
|
20
|
+
when :==
|
|
21
|
+
standard_message.equal(v[:operand])
|
|
22
|
+
when :!=
|
|
23
|
+
standard_message.not.equal(v[:operand])
|
|
24
|
+
when :=~
|
|
25
|
+
standard_message.matching(v[:operand])
|
|
26
|
+
when :!~
|
|
27
|
+
standard_message.not.matching(v[:operand])
|
|
28
|
+
when :equal?
|
|
29
|
+
standard_message.same(v[:operand])
|
|
30
|
+
when :is_a?, :kind_of?
|
|
31
|
+
standard_message.kind_of(v[:operand])
|
|
32
|
+
when :instance_of?
|
|
33
|
+
standard_message.instance_of(v[:operand])
|
|
34
|
+
when :respond_to?
|
|
35
|
+
standard_message.responding_to(v[:operand])
|
|
36
|
+
when :key?
|
|
37
|
+
standard_message.having_key(v[:operand])
|
|
38
|
+
when :include?
|
|
39
|
+
standard_message.including(v[:operand])
|
|
40
|
+
when :in?
|
|
41
|
+
standard_message.in(v[:operand])
|
|
42
|
+
else
|
|
43
|
+
raise "Unexpected method: #{e[:call].method}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# predicate
|
|
48
|
+
message method_hole(:predicate, _, -> { _1.end_with?('?') }) do |_v, e|
|
|
49
|
+
standard_message.predicate(e[:predicate].method)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# flip
|
|
53
|
+
transform(
|
|
54
|
+
method_hole(
|
|
55
|
+
:call,
|
|
56
|
+
hole(:operand) { !_1.variables.include?(:actual)},
|
|
57
|
+
%i[== != < > <= >= equal? include? in?],
|
|
58
|
+
hole(:actual) { _1.variables.include?(:actual) },
|
|
59
|
+
),
|
|
60
|
+
) do |m|
|
|
61
|
+
method = m[:call].expression.method
|
|
62
|
+
|
|
63
|
+
flipped_method =
|
|
64
|
+
case method
|
|
65
|
+
when :< then :>
|
|
66
|
+
when :> then :<
|
|
67
|
+
when :<= then :>=
|
|
68
|
+
when :>= then :<=
|
|
69
|
+
when :include? then :in?
|
|
70
|
+
when :in? then :include?
|
|
71
|
+
else
|
|
72
|
+
method
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
call(m[:call], m[:actual], flipped_method, m[:operand])
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# length
|
|
79
|
+
message capture(:act, _.length) == const(:exp) do |v|
|
|
80
|
+
standard_message.length_of(v[:exp], v[:act])
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# between
|
|
84
|
+
message _.between?(const(:min), const(:max)) do |v|
|
|
85
|
+
standard_message.between(v[:min], v[:max])
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# truthy
|
|
89
|
+
message _ do
|
|
90
|
+
standard_message.truthy
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# transform !
|
|
94
|
+
transform !hole(:expression), negate: true do |m|
|
|
95
|
+
m[:expression]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# transform instance_of?
|
|
99
|
+
transform(
|
|
100
|
+
hole(:obj).class == hole(:class),
|
|
101
|
+
hole(:class) == hole(:obj).class,
|
|
102
|
+
) do |m|
|
|
103
|
+
call(m[:root], m[:obj], :instance_of?, m[:class])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# transform between?
|
|
107
|
+
transform(
|
|
108
|
+
lo { (hole(:value) >= hole(:min)) & (hole(:value) <= hole(:max)) },
|
|
109
|
+
) do |m|
|
|
110
|
+
call(m[:root], m[:value], :between?, m[:min], m[:max])
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# transform comparison
|
|
114
|
+
transform(
|
|
115
|
+
method_hole(:compare, hole(:lhs) <=> hole(:rhs), %i[== != < > <= >=], 0),
|
|
116
|
+
) do |m|
|
|
117
|
+
call(m[:compare], m[:lhs], m[:compare].expression.method, m[:rhs])
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# length expression
|
|
121
|
+
message capture(:act, hole(:object).length) == hole(:exp) do |v, e|
|
|
122
|
+
expression_message.length_of(e[:object], v[:object], v[:exp], v[:act], given)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# match regexp at
|
|
126
|
+
message(
|
|
127
|
+
method_hole(
|
|
128
|
+
:comparison,
|
|
129
|
+
capture(:pos, hole(:lhs) =~ hole(:rhs)),
|
|
130
|
+
%i[== != < > <= >=],
|
|
131
|
+
hole(:operand),
|
|
132
|
+
),
|
|
133
|
+
) do |v, e|
|
|
134
|
+
expression, value, pattern =
|
|
135
|
+
MessageRules.decompose_pattern_matching(e[:lhs], e[:rhs], v[:lhs], v[:rhs])
|
|
136
|
+
|
|
137
|
+
if expression
|
|
138
|
+
expression_message.match_at(
|
|
139
|
+
expression,
|
|
140
|
+
value,
|
|
141
|
+
pattern,
|
|
142
|
+
v[:pos],
|
|
143
|
+
e[:comparison].method,
|
|
144
|
+
v[:operand],
|
|
145
|
+
given,
|
|
146
|
+
)
|
|
147
|
+
else
|
|
148
|
+
expression_message.truthy(e[:comparison], v[:comparison], given)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# comparison expression
|
|
153
|
+
message method_hole(:comparison, hole(:lhs), %i[== != < > <= >=], hole(:rhs)) do |v, e|
|
|
154
|
+
expression_message.comparison(e[:comparison], v[:lhs], v[:rhs], given)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# general binary expression
|
|
158
|
+
general_ops = %i[<=> equal? is_a? kind_of? instance_of? respond_to? key? include? in?]
|
|
159
|
+
message method_hole(:call, hole(:lhs), general_ops, hole(:rhs)) do |v, e|
|
|
160
|
+
case e[:call].method
|
|
161
|
+
when :<=>
|
|
162
|
+
# <=> returns nil if operands are not comparable
|
|
163
|
+
expression_message.comparable_to(e[:lhs], v[:lhs], v[:rhs], given)
|
|
164
|
+
when :equal?
|
|
165
|
+
expression_message.same(e[:lhs], e[:rhs], v[:lhs], v[:rhs], given)
|
|
166
|
+
when :is_a?, :kind_of?
|
|
167
|
+
expression_message.kind_of(e[:lhs], v[:lhs], v[:rhs], given)
|
|
168
|
+
when :instance_of?
|
|
169
|
+
expression_message.instance_of(e[:lhs], v[:lhs], v[:rhs], given)
|
|
170
|
+
when :respond_to?
|
|
171
|
+
expression_message.responding_to(e[:lhs], v[:lhs], v[:rhs], given)
|
|
172
|
+
when :key?
|
|
173
|
+
expression_message.having_key(e[:lhs], v[:lhs], v[:rhs], given)
|
|
174
|
+
when :include?
|
|
175
|
+
expression_message.including(e[:lhs], v[:lhs], v[:rhs], given)
|
|
176
|
+
when :in?
|
|
177
|
+
expression_message.in(e[:lhs], v[:lhs], v[:rhs], given)
|
|
178
|
+
else
|
|
179
|
+
raise "Unexpected method: #{e[:call].method}"
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# predicate expression
|
|
184
|
+
message method_hole(:predicate, hole(:receiver), -> { _1.end_with?('?') }) do |v, e|
|
|
185
|
+
expression_message.predicate(e[:receiver], v[:receiver], e[:predicate].method, given)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# match regexp
|
|
189
|
+
message method_hole(:operator, hole(:lhs), %i[=~ !~], hole(:rhs)) do |v, e|
|
|
190
|
+
expression, value, pattern =
|
|
191
|
+
MessageRules.decompose_pattern_matching(e[:lhs], e[:rhs], v[:lhs], v[:rhs])
|
|
192
|
+
|
|
193
|
+
if expression
|
|
194
|
+
expression_message.not_if(e[:operator].method == :!~)
|
|
195
|
+
.matching(expression, value, pattern, given)
|
|
196
|
+
else
|
|
197
|
+
expression_message.truthy(e[:operator], v[:operator], given)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# between expression
|
|
202
|
+
message hole(:value).between?(hole(:min), hole(:max)) do |v, e|
|
|
203
|
+
expression_message.between(e[:value], v[:value], v[:min], v[:max], given)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# any expression
|
|
207
|
+
message hole(:expression) do |v, e|
|
|
208
|
+
expression_message.truthy(e[:expression], v[:expression], given)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
module MessageRules
|
|
213
|
+
def self.decompose_pattern_matching(e_lhs, e_rhs, v_lhs, v_rhs)
|
|
214
|
+
if v_lhs.is_a?(String) && v_rhs.is_a?(Regexp)
|
|
215
|
+
[e_lhs, v_lhs, v_rhs]
|
|
216
|
+
elsif v_lhs.is_a?(Regexp) && v_rhs.is_a?(String)
|
|
217
|
+
[e_rhs, v_rhs, v_lhs]
|
|
218
|
+
else
|
|
219
|
+
nil
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|