super_diff 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (206) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +117 -89
  3. data/lib/super_diff.rb +33 -47
  4. data/lib/super_diff/active_record.rb +41 -0
  5. data/lib/super_diff/active_record/diff_formatters.rb +10 -0
  6. data/lib/super_diff/active_record/diff_formatters/active_record_relation.rb +23 -0
  7. data/lib/super_diff/active_record/differs.rb +10 -0
  8. data/lib/super_diff/active_record/differs/active_record_relation.rb +30 -0
  9. data/lib/super_diff/active_record/object_inspection.rb +14 -0
  10. data/lib/super_diff/active_record/object_inspection/inspectors.rb +16 -0
  11. data/lib/super_diff/active_record/object_inspection/inspectors/active_record_model.rb +38 -0
  12. data/lib/super_diff/active_record/object_inspection/inspectors/active_record_relation.rb +18 -0
  13. data/lib/super_diff/active_record/object_inspection/map_extension.rb +18 -0
  14. data/lib/super_diff/active_record/operation_sequences.rb +10 -0
  15. data/lib/super_diff/active_record/operation_sequences/active_record_relation.rb +16 -0
  16. data/lib/super_diff/active_record/operational_sequencers.rb +14 -0
  17. data/lib/super_diff/active_record/operational_sequencers/active_record_model.rb +19 -0
  18. data/lib/super_diff/active_record/operational_sequencers/active_record_relation.rb +24 -0
  19. data/lib/super_diff/active_support.rb +33 -0
  20. data/lib/super_diff/active_support/diff_formatters.rb +10 -0
  21. data/lib/super_diff/active_support/diff_formatters/hash_with_indifferent_access.rb +36 -0
  22. data/lib/super_diff/active_support/differs.rb +10 -0
  23. data/lib/super_diff/active_support/differs/hash_with_indifferent_access.rb +36 -0
  24. data/lib/super_diff/active_support/object_inspection.rb +14 -0
  25. data/lib/super_diff/active_support/object_inspection/inspectors.rb +12 -0
  26. data/lib/super_diff/active_support/object_inspection/inspectors/hash_with_indifferent_access.rb +18 -0
  27. data/lib/super_diff/active_support/object_inspection/map_extension.rb +15 -0
  28. data/lib/super_diff/active_support/operation_sequences.rb +10 -0
  29. data/lib/super_diff/active_support/operation_sequences/hash_with_indifferent_access.rb +16 -0
  30. data/lib/super_diff/active_support/operational_sequencers.rb +10 -0
  31. data/lib/super_diff/active_support/operational_sequencers/hash_with_indifferent_access.rb +21 -0
  32. data/lib/super_diff/colorized_document_extensions.rb +17 -0
  33. data/lib/super_diff/csi.rb +45 -15
  34. data/lib/super_diff/csi/bold_sequence.rb +9 -0
  35. data/lib/super_diff/csi/color.rb +62 -0
  36. data/lib/super_diff/csi/color_sequence_block.rb +28 -0
  37. data/lib/super_diff/csi/colorized_document.rb +72 -0
  38. data/lib/super_diff/csi/document.rb +183 -0
  39. data/lib/super_diff/csi/eight_bit_color.rb +72 -26
  40. data/lib/super_diff/csi/four_bit_color.rb +63 -29
  41. data/lib/super_diff/csi/twenty_four_bit_color.rb +79 -18
  42. data/lib/super_diff/csi/uncolorized_document.rb +29 -0
  43. data/lib/super_diff/diff_formatter.rb +10 -15
  44. data/lib/super_diff/diff_formatters.rb +10 -1
  45. data/lib/super_diff/diff_formatters/base.rb +12 -17
  46. data/lib/super_diff/diff_formatters/collection.rb +81 -50
  47. data/lib/super_diff/diff_formatters/{object.rb → custom_object.rb} +12 -9
  48. data/lib/super_diff/diff_formatters/default_object.rb +48 -0
  49. data/lib/super_diff/diff_formatters/multiline_string.rb +31 -0
  50. data/lib/super_diff/differ.rb +35 -32
  51. data/lib/super_diff/differs.rb +16 -1
  52. data/lib/super_diff/differs/array.rb +2 -2
  53. data/lib/super_diff/differs/base.rb +11 -21
  54. data/lib/super_diff/differs/custom_object.rb +26 -0
  55. data/lib/super_diff/differs/default_object.rb +25 -0
  56. data/lib/super_diff/differs/empty.rb +1 -1
  57. data/lib/super_diff/differs/hash.rb +2 -2
  58. data/lib/super_diff/differs/{multi_line_string.rb → multiline_string.rb} +6 -5
  59. data/lib/super_diff/equality_matcher.rb +9 -22
  60. data/lib/super_diff/equality_matchers.rb +19 -1
  61. data/lib/super_diff/equality_matchers/array.rb +6 -4
  62. data/lib/super_diff/equality_matchers/base.rb +8 -16
  63. data/lib/super_diff/equality_matchers/default.rb +60 -0
  64. data/lib/super_diff/equality_matchers/hash.rb +6 -4
  65. data/lib/super_diff/equality_matchers/{multi_line_string.rb → multiline_string.rb} +9 -6
  66. data/lib/super_diff/equality_matchers/primitive.rb +34 -0
  67. data/lib/super_diff/equality_matchers/{single_line_string.rb → singleline_string.rb} +7 -5
  68. data/lib/super_diff/helpers.rb +17 -81
  69. data/lib/super_diff/no_differ_available_error.rb +22 -0
  70. data/lib/super_diff/{errors.rb → no_operational_sequencer_available_error.rb} +0 -0
  71. data/lib/super_diff/object_inspection.rb +24 -0
  72. data/lib/super_diff/object_inspection/inspection_tree.rb +144 -0
  73. data/lib/super_diff/object_inspection/inspector.rb +27 -0
  74. data/lib/super_diff/object_inspection/inspectors.rb +18 -0
  75. data/lib/super_diff/object_inspection/inspectors/array.rb +22 -0
  76. data/lib/super_diff/object_inspection/inspectors/custom_object.rb +27 -0
  77. data/lib/super_diff/object_inspection/inspectors/default_object.rb +47 -0
  78. data/lib/super_diff/object_inspection/inspectors/hash.rb +22 -0
  79. data/lib/super_diff/object_inspection/inspectors/primitive.rb +13 -0
  80. data/lib/super_diff/object_inspection/inspectors/string.rb +13 -0
  81. data/lib/super_diff/object_inspection/map.rb +28 -0
  82. data/lib/super_diff/object_inspection/nodes.rb +49 -0
  83. data/lib/super_diff/object_inspection/nodes/base.rb +86 -0
  84. data/lib/super_diff/object_inspection/nodes/break.rb +15 -0
  85. data/lib/super_diff/object_inspection/nodes/inspection.rb +15 -0
  86. data/lib/super_diff/object_inspection/nodes/nesting.rb +16 -0
  87. data/lib/super_diff/object_inspection/nodes/text.rb +15 -0
  88. data/lib/super_diff/object_inspection/nodes/when_empty.rb +30 -0
  89. data/lib/super_diff/object_inspection/nodes/when_multiline.rb +22 -0
  90. data/lib/super_diff/object_inspection/nodes/when_non_empty.rb +30 -0
  91. data/lib/super_diff/object_inspection/nodes/when_singleline.rb +24 -0
  92. data/lib/super_diff/operation_sequences.rb +9 -0
  93. data/lib/super_diff/operation_sequences/base.rb +1 -1
  94. data/lib/super_diff/operation_sequences/{object.rb → custom_object.rb} +4 -3
  95. data/lib/super_diff/operation_sequences/default_object.rb +25 -0
  96. data/lib/super_diff/operational_sequencer.rb +23 -18
  97. data/lib/super_diff/operational_sequencers.rb +12 -1
  98. data/lib/super_diff/operational_sequencers/array.rb +65 -62
  99. data/lib/super_diff/operational_sequencers/base.rb +18 -26
  100. data/lib/super_diff/operational_sequencers/custom_object.rb +35 -0
  101. data/lib/super_diff/operational_sequencers/{object.rb → default_object.rb} +21 -11
  102. data/lib/super_diff/operational_sequencers/hash.rb +8 -5
  103. data/lib/super_diff/operational_sequencers/{multi_line_string.rb → multiline_string.rb} +11 -6
  104. data/lib/super_diff/operations.rb +6 -0
  105. data/lib/super_diff/operations/binary_operation.rb +14 -34
  106. data/lib/super_diff/operations/unary_operation.rb +11 -2
  107. data/lib/super_diff/rails.rb +1 -0
  108. data/lib/super_diff/recursion_guard.rb +47 -0
  109. data/lib/super_diff/rspec-rails.rb +2 -0
  110. data/lib/super_diff/rspec.rb +52 -8
  111. data/lib/super_diff/rspec/augmented_matcher.rb +98 -0
  112. data/lib/super_diff/rspec/configuration.rb +31 -0
  113. data/lib/super_diff/rspec/differ.rb +60 -16
  114. data/lib/super_diff/rspec/differs.rb +13 -0
  115. data/lib/super_diff/rspec/differs/collection_containing_exactly.rb +23 -0
  116. data/lib/super_diff/rspec/differs/partial_array.rb +22 -0
  117. data/lib/super_diff/rspec/differs/partial_hash.rb +22 -0
  118. data/lib/super_diff/rspec/differs/partial_object.rb +22 -0
  119. data/lib/super_diff/rspec/matcher_text_builders.rb +24 -0
  120. data/lib/super_diff/rspec/matcher_text_builders/base.rb +155 -0
  121. data/lib/super_diff/rspec/matcher_text_builders/be_predicate.rb +78 -0
  122. data/lib/super_diff/rspec/matcher_text_builders/contain_exactly.rb +14 -0
  123. data/lib/super_diff/rspec/matcher_text_builders/match.rb +23 -0
  124. data/lib/super_diff/rspec/matcher_text_builders/raise_error.rb +13 -0
  125. data/lib/super_diff/rspec/matcher_text_builders/respond_to.rb +99 -0
  126. data/lib/super_diff/rspec/matcher_text_template.rb +240 -0
  127. data/lib/super_diff/rspec/monkey_patches.rb +601 -98
  128. data/lib/super_diff/rspec/object_inspection.rb +8 -0
  129. data/lib/super_diff/rspec/object_inspection/inspectors.rb +24 -0
  130. data/lib/super_diff/rspec/object_inspection/inspectors/collection_containing_exactly.rb +19 -0
  131. data/lib/super_diff/rspec/object_inspection/inspectors/partial_array.rb +22 -0
  132. data/lib/super_diff/rspec/object_inspection/inspectors/partial_hash.rb +21 -0
  133. data/lib/super_diff/rspec/object_inspection/inspectors/partial_object.rb +21 -0
  134. data/lib/super_diff/rspec/object_inspection/map_extension.rb +23 -0
  135. data/lib/super_diff/rspec/operational_sequencers.rb +22 -0
  136. data/lib/super_diff/rspec/operational_sequencers/collection_containing_exactly.rb +97 -0
  137. data/lib/super_diff/rspec/operational_sequencers/partial_array.rb +23 -0
  138. data/lib/super_diff/rspec/operational_sequencers/partial_hash.rb +32 -0
  139. data/lib/super_diff/rspec/operational_sequencers/partial_object.rb +64 -0
  140. data/lib/super_diff/version.rb +1 -1
  141. data/spec/examples.txt +328 -46
  142. data/spec/integration/rails/active_record_spec.rb +19 -0
  143. data/spec/integration/rails/hash_with_indifferent_access_spec.rb +19 -0
  144. data/spec/integration/rspec/be_falsey_matcher_spec.rb +53 -0
  145. data/spec/integration/rspec/be_matcher_spec.rb +565 -0
  146. data/spec/integration/rspec/be_nil_matcher_spec.rb +53 -0
  147. data/spec/integration/rspec/be_predicate_matcher_spec.rb +546 -0
  148. data/spec/integration/rspec/be_truthy_matcher_spec.rb +57 -0
  149. data/spec/integration/rspec/contain_exactly_matcher_spec.rb +368 -0
  150. data/spec/integration/rspec/eq_matcher_spec.rb +874 -0
  151. data/spec/integration/rspec/have_attributes_matcher_spec.rb +299 -0
  152. data/spec/integration/rspec/include_matcher_spec.rb +350 -0
  153. data/spec/integration/rspec/match_matcher_spec.rb +1258 -0
  154. data/spec/integration/rspec/raise_error_matcher_spec.rb +350 -0
  155. data/spec/integration/rspec/respond_to_matcher_spec.rb +994 -0
  156. data/spec/integration/rspec/unhandled_errors_spec.rb +94 -0
  157. data/spec/spec_helper.rb +19 -4
  158. data/spec/support/colorizer.rb +9 -0
  159. data/spec/support/command_runner.rb +4 -0
  160. data/spec/support/integration/helpers.rb +179 -0
  161. data/spec/support/integration/matchers/produce_output_when_run_matcher.rb +79 -41
  162. data/spec/support/models/a.rb +11 -0
  163. data/spec/support/models/active_record/person.rb +26 -0
  164. data/spec/support/models/active_record/shipping_address.rb +29 -0
  165. data/spec/support/models/customer.rb +24 -0
  166. data/spec/support/models/empty_class.rb +6 -0
  167. data/spec/support/models/item.rb +10 -0
  168. data/spec/support/models/order.rb +9 -0
  169. data/spec/support/models/person.rb +20 -0
  170. data/spec/support/models/player.rb +33 -0
  171. data/spec/support/models/shipping_address.rb +34 -0
  172. data/spec/support/ruby_versions.rb +7 -0
  173. data/spec/support/shared_examples/active_record.rb +338 -0
  174. data/spec/support/shared_examples/hash_with_indifferent_access.rb +233 -0
  175. data/spec/unit/equality_matcher_spec.rb +579 -171
  176. data/spec/unit/object_inspection_spec.rb +1092 -0
  177. data/spec/unit/rspec/matchers/be_compared_to_spec.rb +23 -0
  178. data/spec/unit/rspec/matchers/be_falsey_spec.rb +9 -0
  179. data/spec/unit/rspec/matchers/be_nil_spec.rb +9 -0
  180. data/spec/unit/rspec/matchers/be_predicate_spec.rb +31 -0
  181. data/spec/unit/rspec/matchers/be_spec.rb +17 -0
  182. data/spec/unit/rspec/matchers/be_truthy_spec.rb +9 -0
  183. data/spec/unit/rspec/matchers/contain_exactly_spec.rb +11 -0
  184. data/spec/unit/rspec/matchers/eq_spec.rb +9 -0
  185. data/spec/unit/rspec/matchers/have_attributes_spec.rb +11 -0
  186. data/spec/unit/rspec/matchers/include_spec.rb +21 -0
  187. data/spec/unit/rspec/matchers/match_spec.rb +9 -0
  188. data/spec/unit/rspec/matchers/raise_error_spec.rb +29 -0
  189. data/spec/unit/rspec/matchers/respond_to_spec.rb +78 -0
  190. data/super_diff.gemspec +4 -2
  191. metadata +231 -34
  192. data/lib/super_diff/csi/color_helper.rb +0 -52
  193. data/lib/super_diff/csi/eight_bit_sequence.rb +0 -27
  194. data/lib/super_diff/csi/four_bit_sequence.rb +0 -24
  195. data/lib/super_diff/csi/sequence.rb +0 -22
  196. data/lib/super_diff/csi/twenty_four_bit_sequence.rb +0 -27
  197. data/lib/super_diff/diff_formatters/multi_line_string.rb +0 -31
  198. data/lib/super_diff/differs/object.rb +0 -68
  199. data/lib/super_diff/equality_matchers/object.rb +0 -18
  200. data/lib/super_diff/value_inspection.rb +0 -11
  201. data/spec/integration/rspec_spec.rb +0 -261
  202. data/spec/support/color_helper.rb +0 -49
  203. data/spec/support/person.rb +0 -23
  204. data/spec/support/person_diff_formatter.rb +0 -15
  205. data/spec/support/person_operation_sequence.rb +0 -14
  206. data/spec/support/person_operational_sequencer.rb +0 -19
@@ -0,0 +1,240 @@
1
+ module SuperDiff
2
+ module RSpec
3
+ class MatcherTextTemplate
4
+ MAX_LINE_LENGTH = 100
5
+
6
+ def self.generate(&block)
7
+ new(&block).to_s
8
+ end
9
+
10
+ def initialize
11
+ @tokens = []
12
+
13
+ if block_given?
14
+ yield self
15
+ end
16
+ end
17
+
18
+ def add_text(*args, &block)
19
+ add_token(PlainText, *args, &block)
20
+ end
21
+
22
+ def add_text_in_color(*args, &block)
23
+ add_token(ColorizedText, *args, &block)
24
+ end
25
+
26
+ def add_text_in_singleline_mode(*args, &block)
27
+ add_token(PlainTextInSinglelineMode, *args, &block)
28
+ end
29
+
30
+ def add_text_in_multiline_mode(*args, &block)
31
+ add_token(PlainTextInMultilineMode, *args, &block)
32
+ end
33
+
34
+ def add_list_in_color(*args, &block)
35
+ add_token(ColorizedList, *args, &block)
36
+ end
37
+
38
+ def add_break(*args, &block)
39
+ add_token(Break, *args, &block)
40
+ end
41
+
42
+ def insert(*args, &block)
43
+ add_token(Insertion, *args, &block)
44
+ end
45
+
46
+ def length_of_first_paragraph
47
+ Csi.decolorize(to_string_in_singleline_mode).
48
+ split(/\n\n/).
49
+ first.
50
+ length
51
+ end
52
+
53
+ def to_s(as_single_line: nil)
54
+ if length_of_first_paragraph > MAX_LINE_LENGTH || as_single_line == false
55
+ to_string_in_multiline_mode
56
+ elsif length_of_first_paragraph <= MAX_LINE_LENGTH || as_single_line == true
57
+ to_string_in_singleline_mode
58
+ end
59
+ end
60
+
61
+ def to_string_in_singleline_mode
62
+ tokens.map(&:to_string_in_singleline_mode).join
63
+ end
64
+
65
+ def to_string_in_multiline_mode
66
+ tokens.map(&:to_string_in_multiline_mode).join
67
+ end
68
+
69
+ private
70
+
71
+ attr_reader :tokens
72
+
73
+ def add_token(klass, *args, &block)
74
+ tokens << klass.new(*args, &block)
75
+ end
76
+
77
+ class Base
78
+ def to_string_in_singleline_mode
79
+ raise NotImplementedError.new(
80
+ "#{self.class} must support #to_string_in_singleline_mode",
81
+ )
82
+ end
83
+
84
+ def to_string_in_multiline_mode
85
+ raise NotImplementedError.new(
86
+ "#{self.class} must support #to_string_in_multiline_mode",
87
+ )
88
+ end
89
+
90
+ def length
91
+ to_string_in_singleline_mode.length
92
+ end
93
+ end
94
+
95
+ class Text < Base
96
+ def initialize(immediate_value = nil, &block)
97
+ @immediate_value = immediate_value
98
+ @block = block
99
+ end
100
+
101
+ def to_string_in_singleline_mode
102
+ to_s
103
+ end
104
+
105
+ def to_string_in_multiline_mode
106
+ to_s
107
+ end
108
+
109
+ def to_s
110
+ raise NotImplementedError.new("#{self.class} must support #to_s")
111
+ end
112
+
113
+ protected
114
+
115
+ attr_reader :immediate_value, :block
116
+
117
+ def evaluate
118
+ if immediate_value && block
119
+ raise ArgumentError.new(
120
+ "Cannot provide both immediate value and block",
121
+ )
122
+ end
123
+
124
+ immediate_value || block.call
125
+ end
126
+
127
+ def to_sentence(values)
128
+ case values.length
129
+ when 0
130
+ ""
131
+ when 1
132
+ values[0]
133
+ else
134
+ # TODO: Use Oxford comma
135
+ values[0..-2].join(", ") + " and #{values[-1]}"
136
+ end
137
+ end
138
+ end
139
+
140
+ class PlainText < Text
141
+ def to_s
142
+ evaluate.to_s
143
+ end
144
+ end
145
+
146
+ class ColorizedText < Text
147
+ def initialize(color, *args, &block)
148
+ super(*args, &block)
149
+
150
+ @color = color
151
+ end
152
+
153
+ def to_s
154
+ colorizer.wrap(evaluate.to_s, color)
155
+ end
156
+
157
+ def length
158
+ evaluate.to_s.length
159
+ end
160
+
161
+ private
162
+
163
+ attr_reader :color
164
+
165
+ def colorizer
166
+ ::RSpec::Core::Formatters::ConsoleCodes
167
+ end
168
+ end
169
+
170
+ class ColorizedList < Text
171
+ def initialize(color, *args, &block)
172
+ super(*args, &block)
173
+
174
+ @color = color
175
+ end
176
+
177
+ def to_s
178
+ to_sentence(colorized_values)
179
+ end
180
+
181
+ private
182
+
183
+ attr_reader :color
184
+
185
+ def colorized_values
186
+ evaluate.map do |value|
187
+ colorizer.wrap(
188
+ ::RSpec::Support::ObjectFormatter.format(value),
189
+ color,
190
+ )
191
+ end
192
+ end
193
+
194
+ def colorizer
195
+ ::RSpec::Core::Formatters::ConsoleCodes
196
+ end
197
+ end
198
+
199
+ class PlainTextInSinglelineMode < Text
200
+ def to_string_in_singleline_mode
201
+ evaluate.to_s
202
+ end
203
+
204
+ def to_string_in_multiline_mode
205
+ ""
206
+ end
207
+ end
208
+
209
+ class PlainTextInMultilineMode < Text
210
+ def to_string_in_singleline_mode
211
+ ""
212
+ end
213
+
214
+ def to_string_in_multiline_mode
215
+ evaluate.to_s
216
+ end
217
+ end
218
+
219
+ class Break < Base
220
+ def to_string_in_singleline_mode
221
+ " "
222
+ end
223
+
224
+ def to_string_in_multiline_mode
225
+ "\n"
226
+ end
227
+ end
228
+
229
+ class Insertion < Text
230
+ def to_string_in_singleline_mode
231
+ evaluate.to_string_in_singleline_mode
232
+ end
233
+
234
+ def to_string_in_multiline_mode
235
+ evaluate.to_string_in_multiline_mode
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
@@ -1,122 +1,625 @@
1
- RSpec::Expectations.instance_eval do
2
- def differ
3
- SuperDiff::RSpec::Differ
4
- end
5
- end
6
-
7
1
  # rubocop:disable all
8
- RSpec::Core::Formatters::ConsoleCodes.instance_eval do
9
- # UPDATE: Patch so it returns nothing if code_or_symbol is nil
10
- def console_code_for(code_or_symbol)
11
- if code_or_symbol
12
- if (config_method = config_colors_to_methods[code_or_symbol])
13
- console_code_for RSpec.configuration.__send__(config_method)
14
- elsif RSpec::Core::Formatters::ConsoleCodes::VT100_CODE_VALUES.key?(code_or_symbol)
15
- code_or_symbol
16
- else
17
- RSpec::Core::Formatters::ConsoleCodes::VT100_CODES.fetch(code_or_symbol) do
18
- console_code_for(:white)
2
+ require "rspec/matchers"
3
+ require "rspec/expectations/fail_with"
4
+ require "rspec/expectations/handler"
5
+ require "rspec/support/object_formatter"
6
+ require "rspec/matchers/built_in/be"
7
+ require "rspec/matchers/built_in/contain_exactly"
8
+ require "rspec/matchers/built_in/eq"
9
+ require "rspec/matchers/built_in/have_attributes"
10
+ require "rspec/matchers/built_in/include"
11
+ require "rspec/matchers/built_in/match"
12
+
13
+ module RSpec
14
+ module Expectations
15
+ def self.differ
16
+ SuperDiff::RSpec::Differ
17
+ end
18
+
19
+ module ExpectationHelper
20
+ def self.handle_failure(matcher, message, failure_message_method)
21
+ message = message.call if message.respond_to?(:call)
22
+ message ||= matcher.__send__(failure_message_method)
23
+
24
+ if matcher.respond_to?(:diffable?) && matcher.diffable?
25
+ # Look for expected_for_diff and actual_for_diff if possible
26
+ expected =
27
+ if matcher.respond_to?(:expected_for_diff)
28
+ matcher.expected_for_diff
29
+ else
30
+ matcher.expected
31
+ end
32
+
33
+ actual =
34
+ if matcher.respond_to?(:actual_for_diff)
35
+ matcher.actual_for_diff
36
+ else
37
+ matcher.actual
38
+ end
39
+
40
+ ::RSpec::Expectations.fail_with(message, expected, actual)
41
+ else
42
+ ::RSpec::Expectations.fail_with(message)
19
43
  end
20
44
  end
21
45
  end
22
46
  end
23
47
 
24
- # UPDATE: Patch so it does not apply a color if code_or_symbol is nil
25
- def wrap(text, code_or_symbol)
26
- if RSpec.configuration.color_enabled? && code = console_code_for(code_or_symbol)
27
- "\e[#{code}m#{text}\e[0m"
28
- else
29
- text
48
+ module Core
49
+ module Formatters
50
+ module ConsoleCodes
51
+ # Patch so it returns nothing if code_or_symbol is nil, and that it uses
52
+ # code_or_symbol if it can't be found in VT100_CODE_VALUES to allow for
53
+ # customization
54
+ def self.console_code_for(code_or_symbol)
55
+ if code_or_symbol
56
+ if (config_method = config_colors_to_methods[code_or_symbol])
57
+ console_code_for RSpec.configuration.__send__(config_method)
58
+ elsif RSpec::Core::Formatters::ConsoleCodes::VT100_CODE_VALUES.key?(code_or_symbol)
59
+ code_or_symbol
60
+ else
61
+ RSpec::Core::Formatters::ConsoleCodes::VT100_CODES.fetch(code_or_symbol) do
62
+ code_or_symbol
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ # Patch so it does not apply a color if code_or_symbol is nil
69
+ def self.wrap(text, code_or_symbol)
70
+ if RSpec.configuration.color_enabled? && code = console_code_for(code_or_symbol)
71
+ "\e[#{code}m#{text}\e[0m"
72
+ else
73
+ text
74
+ end
75
+ end
76
+ end
77
+
78
+ class ExceptionPresenter
79
+ # UPDATE: Copy from SyntaxHighlighter::CodeRayImplementation
80
+ RESET_CODE = "\e[0m"
81
+
82
+ def initialize(exception, example, options={})
83
+ @exception = exception
84
+ @example = example
85
+ @message_color = options.fetch(:message_color) { RSpec.configuration.failure_color }
86
+ @description = options.fetch(:description) { example.full_description }
87
+ @detail_formatter = options.fetch(:detail_formatter) { Proc.new {} }
88
+ @extra_detail_formatter = options.fetch(:extra_detail_formatter) { Proc.new {} }
89
+ @backtrace_formatter = options.fetch(:backtrace_formatter) { RSpec.configuration.backtrace_formatter }
90
+ @indentation = options.fetch(:indentation, 2)
91
+ @skip_shared_group_trace = options.fetch(:skip_shared_group_trace, false)
92
+ # Patch to convert options[:failure_lines] to groups
93
+ if options.include?(:failure_lines)
94
+ @failure_line_groups = {
95
+ lines: options[:failure_lines],
96
+ already_colored: false
97
+ }
98
+ end
99
+ end
100
+
101
+ # Override to only color uncolored lines in red
102
+ def colorized_message_lines(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
103
+ lines = failure_line_groups.flat_map do |group|
104
+ if group[:already_colored]
105
+ group[:lines]
106
+ else
107
+ group[:lines].map do |line|
108
+ colorizer.wrap(line, message_color)
109
+ end
110
+ end
111
+ end
112
+
113
+ add_shared_group_lines(lines, colorizer)
114
+ end
115
+
116
+ private
117
+
118
+ def add_shared_group_lines(lines, colorizer)
119
+ return lines if @skip_shared_group_trace
120
+
121
+ example.metadata[:shared_group_inclusion_backtrace].each do |frame|
122
+ # Use red instead of the default color
123
+ lines << colorizer.wrap(frame.description, :failure)
124
+ end
125
+
126
+ lines
127
+ end
128
+
129
+ # Considering that `failure_slash_error_lines` is already colored,
130
+ # extract this from the other lines so that they, too, can be colored,
131
+ # later
132
+ def failure_line_groups
133
+ @failure_line_groups ||= [].tap do |groups|
134
+ groups << {
135
+ lines: failure_slash_error_lines,
136
+ already_colored: true
137
+ }
138
+
139
+ sections = [failure_slash_error_lines, exception_lines]
140
+ separate_groups = (
141
+ sections.any? { |section| section.size > 1 } &&
142
+ !exception_lines.first.empty?
143
+ )
144
+ if separate_groups
145
+ groups << { lines: [''], already_colored: true }
146
+ end
147
+ already_has_coloration = exception_lines.any? do |line|
148
+ line.match?(/\e\[\d+m/)
149
+ end
150
+
151
+ groups << {
152
+ lines: exception_lines[0..0],
153
+ already_colored: already_has_coloration
154
+ }
155
+ groups << {
156
+ lines: exception_lines[1..-1] + extra_failure_lines,
157
+ already_colored: true
158
+ }
159
+ end
160
+ end
161
+
162
+ # Style the first part in white and don't style the snippet of the line
163
+ def failure_slash_error_lines
164
+ lines = read_failed_lines
165
+
166
+ failure_slash_error = ConsoleCodes.wrap("Failure/Error: ", :bold)
167
+
168
+ if lines.count == 1
169
+ lines[0] = failure_slash_error + lines[0].strip
170
+ else
171
+ least_indentation = SnippetExtractor.least_indentation_from(lines)
172
+ lines = lines.map do |line|
173
+ line.sub(/^#{least_indentation}/, ' ')
174
+ end
175
+ lines.unshift(failure_slash_error)
176
+ end
177
+
178
+ lines
179
+ end
180
+
181
+ # Exclude this file from being included in backtraces, so that the
182
+ # SnippetExtractor prints the right thing
183
+ def find_failed_line
184
+ line_regex = RSpec.configuration.in_project_source_dir_regex
185
+ loaded_spec_files = RSpec.configuration.loaded_spec_files
186
+
187
+ exception_backtrace.find do |line|
188
+ next unless (line_path = line[/(.+?):(\d+)(|:\d+)/, 1])
189
+ path = File.expand_path(line_path)
190
+ path != __FILE__ && (loaded_spec_files.include?(path) || path =~ line_regex)
191
+ end || exception_backtrace.first
192
+ end
193
+ end
194
+
195
+ class SyntaxHighlighter
196
+ private
197
+
198
+ def implementation
199
+ RSpec::Core::Formatters::SyntaxHighlighter::NoSyntaxHighlightingImplementation
200
+ end
201
+ end
30
202
  end
31
203
  end
32
- end
33
204
 
34
- RSpec::Core::Formatters::ExceptionPresenter.class_eval do
35
- # UPDATE: Copy from SyntaxHighlighter::CodeRayImplementation
36
- RESET_CODE = "\e[0m"
37
-
38
- def initialize(exception, example, options={})
39
- @exception = exception
40
- @example = example
41
- # UPDATE: Use no color by default
42
- @message_color = options[:message_color]
43
- @description = options.fetch(:description) { example.full_description }
44
- @detail_formatter = options.fetch(:detail_formatter) { Proc.new {} }
45
- @extra_detail_formatter = options.fetch(:extra_detail_formatter) { Proc.new {} }
46
- @backtrace_formatter = options.fetch(:backtrace_formatter) { RSpec.configuration.backtrace_formatter }
47
- @indentation = options.fetch(:indentation, 2)
48
- @skip_shared_group_trace = options.fetch(:skip_shared_group_trace, false)
49
- @failure_lines = options[:failure_lines]
205
+ module Support
206
+ class ObjectFormatter
207
+ # Override to use our formatting algorithm
208
+ def format(value)
209
+ SuperDiff::ObjectInspection.inspect(value, as_single_line: true)
210
+ end
211
+ end
50
212
  end
51
213
 
52
- def add_shared_group_lines(lines, colorizer)
53
- return lines if @skip_shared_group_trace
214
+ module Matchers
215
+ class ExpectedsForMultipleDiffs
216
+ # Add a key for different sides
217
+ def self.from(expected)
218
+ return expected if self === expected
54
219
 
55
- example.metadata[:shared_group_inclusion_backtrace].each do |frame|
56
- # Update: Use red instead of the default color
57
- lines << colorizer.wrap(frame.description, :failure)
58
- end
220
+ text =
221
+ colorizer.wrap("Diff:", SuperDiff::COLORS.fetch(:header)) +
222
+ "\n\n" +
223
+ colorizer.wrap(
224
+ "┌ (Key) ──────────────────────────┐",
225
+ SuperDiff::COLORS.fetch(:border)
226
+ ) +
227
+ "\n" +
228
+ colorizer.wrap("│ ", SuperDiff::COLORS.fetch(:border)) +
229
+ colorizer.wrap(
230
+ "‹-› in expected, not in actual",
231
+ SuperDiff::COLORS.fetch(:alpha)
232
+ ) +
233
+ colorizer.wrap(" │", SuperDiff::COLORS.fetch(:border)) +
234
+ "\n" +
235
+ colorizer.wrap("│ ", SuperDiff::COLORS.fetch(:border)) +
236
+ colorizer.wrap(
237
+ "‹+› in actual, not in expected",
238
+ SuperDiff::COLORS.fetch(:beta)
239
+ ) +
240
+ colorizer.wrap(" │", SuperDiff::COLORS.fetch(:border)) +
241
+ "\n" +
242
+ colorizer.wrap("│ ", SuperDiff::COLORS.fetch(:border)) +
243
+ "‹ › in both expected and actual" +
244
+ colorizer.wrap(" │", SuperDiff::COLORS.fetch(:border)) +
245
+ "\n" +
246
+ colorizer.wrap(
247
+ "└─────────────────────────────────┘",
248
+ SuperDiff::COLORS.fetch(:border)
249
+ )
59
250
 
60
- lines
61
- end
251
+ new([[expected, text]])
252
+ end
253
+
254
+ def self.colorizer
255
+ RSpec::Core::Formatters::ConsoleCodes
256
+ end
257
+
258
+ # Add an extra line break
259
+ def message_with_diff(message, differ, actual)
260
+ diff = diffs(differ, actual)
261
+
262
+ if diff.empty?
263
+ message
264
+ else
265
+ "#{message.rstrip}\n\n#{diff}"
266
+ end
267
+ end
268
+
269
+ private
62
270
 
63
- # UPDATE: Style the first part in blue and the snippet of the line that failed
64
- # in white
65
- def failure_slash_error_lines
66
- lines = read_failed_lines
67
-
68
- failure_slash_error = RSpec::Core::Formatters::ConsoleCodes.wrap(
69
- "Failure/Error: ",
70
- :detail
71
- )
72
-
73
- if lines.count == 1
74
- lines[0] =
75
- failure_slash_error +
76
- RSpec::Core::Formatters::ConsoleCodes.wrap(lines[0].strip, :white)
77
- else
78
- least_indentation =
79
- RSpec::Core::Formatters::SnippetExtractor.least_indentation_from(lines)
80
- lines = lines.map do |line|
81
- RSpec::Core::Formatters::ConsoleCodes.wrap(
82
- line.sub(/^#{least_indentation}/, ' '),
83
- :white
84
- )
85
- end
86
- lines.unshift(failure_slash_error)
271
+ # Add extra line breaks in between diffs, and colorize the word "Diff"
272
+ def diffs(differ, actual)
273
+ @expected_list.map do |(expected, diff_label)|
274
+ diff = differ.diff(actual, expected)
275
+ next if diff.strip.empty?
276
+ diff_label + diff
277
+ end.compact.join("\n\n")
278
+ end
87
279
  end
88
280
 
89
- lines
90
- end
91
- end
281
+ module BuiltIn
282
+ class Be
283
+ prepend SuperDiff::RSpec::AugmentedMatcher
92
284
 
93
- RSpec::Matchers::BuiltIn::Eq.class_eval do
94
- def failure_message
95
- "\n" +
96
- colorizer.wrap("expected: #{expected_formatted}\n", :failure) +
97
- colorizer.wrap(" got: #{actual_formatted}\n\n", :success) +
98
- colorizer.wrap("(compared using ==)\n", :detail)
99
- end
285
+ prepend(Module.new do
286
+ def expected_for_matcher_text
287
+ "truthy"
288
+ end
289
+ end)
290
+ end
100
291
 
101
- def failure_message_when_negated
102
- "\n" +
103
- colorizer.wrap("expected: value != #{expected_formatted}\n", :failure) +
104
- colorizer.wrap(" got: #{actual_formatted}\n\n", :success) +
105
- colorizer.wrap("(compared using ==)\n", :detail)
106
- end
292
+ class BeComparedTo
293
+ prepend SuperDiff::RSpec::AugmentedMatcher
107
294
 
108
- private
295
+ prepend(Module.new do
296
+ def expected_action_for_matcher_text
297
+ if [:==, :===, :=~].include?(@operator)
298
+ "#{@operator}"
299
+ else
300
+ "be #{@operator}"
301
+ end
302
+ end
303
+ end)
304
+ end
109
305
 
110
- def colorizer
111
- RSpec::Core::Formatters::ConsoleCodes
112
- end
113
- end
306
+ class BeTruthy
307
+ prepend SuperDiff::RSpec::AugmentedMatcher
308
+
309
+ prepend(Module.new do
310
+ def expected_action_for_matcher_text
311
+ "be"
312
+ end
313
+
314
+ def expected_for_matcher_text
315
+ "truthy"
316
+ end
317
+ end)
318
+ end
319
+
320
+ class BeFalsey
321
+ prepend SuperDiff::RSpec::AugmentedMatcher
322
+
323
+ prepend(Module.new do
324
+ def expected_action_for_matcher_text
325
+ "be"
326
+ end
114
327
 
115
- RSpec::Core::Formatters::SyntaxHighlighter.class_eval do
116
- private
328
+ def expected_for_matcher_text
329
+ "falsey"
330
+ end
331
+ end)
332
+ end
333
+
334
+ class BeNil
335
+ prepend SuperDiff::RSpec::AugmentedMatcher
336
+
337
+ prepend(Module.new do
338
+ def expected_action_for_matcher_text
339
+ "be"
340
+ end
341
+
342
+ def expected_for_matcher_text
343
+ "nil"
344
+ end
345
+ end)
346
+ end
347
+
348
+ class BePredicate
349
+ prepend SuperDiff::RSpec::AugmentedMatcher
350
+
351
+ prepend(Module.new do
352
+ def actual_for_matcher_text
353
+ actual
354
+ end
355
+
356
+ def expected_for_matcher_text
357
+ expected
358
+ end
359
+
360
+ def expected_action_for_matcher_text
361
+ "return true for"
362
+ end
363
+
364
+ def matcher_text_builder_class
365
+ SuperDiff::RSpec::MatcherTextBuilders::BePredicate
366
+ end
367
+
368
+ def matcher_text_builder_args
369
+ super.merge(
370
+ predicate_accessible: predicate_accessible?,
371
+ private_predicate: private_predicate?,
372
+ expected_predicate_method_name: predicate
373
+ )
374
+ end
375
+ end)
376
+ end
377
+
378
+ class Eq
379
+ prepend SuperDiff::RSpec::AugmentedMatcher
380
+ end
381
+
382
+ class Equal
383
+ prepend SuperDiff::RSpec::AugmentedMatcher
384
+ end
385
+
386
+ class ContainExactly
387
+ prepend SuperDiff::RSpec::AugmentedMatcher
388
+
389
+ prepend(Module.new do
390
+ # Override this method so that the differ knows that this is a partial
391
+ # collection
392
+ def expected_for_diff
393
+ matchers.a_collection_containing_exactly(*expected)
394
+ end
395
+
396
+ private
397
+
398
+ def expected_for_matcher_text
399
+ expected
400
+ end
401
+
402
+ def matcher_text_builder_class
403
+ SuperDiff::RSpec::MatcherTextBuilders::ContainExactly
404
+ end
405
+ end)
406
+ end
407
+
408
+ class Include
409
+ prepend SuperDiff::RSpec::AugmentedMatcher
410
+
411
+ prepend(Module.new do
412
+ # Override this method so that the differ knows that this is a partial
413
+ # array or hash
414
+ def expected_for_diff
415
+ if expecteds.all? { |item| item.is_a?(Hash) }
416
+ matchers.a_collection_including(expecteds.first)
417
+ else
418
+ matchers.a_collection_including(*expecteds)
419
+ end
420
+ end
421
+
422
+ private
423
+
424
+ # Override to capitalize message and add period at end
425
+ def build_failure_message(negated:)
426
+ message = super
427
+
428
+ if actual.respond_to?(:include?)
429
+ message
430
+ elsif message.end_with?(".")
431
+ message.sub("\.$", ", ") + "but it does not respond to `include?`."
432
+ else
433
+ message + "\n\nBut it does not respond to `include?`."
434
+ end
435
+ end
436
+
437
+ # Override to use readable_list_of
438
+ def expected_for_description
439
+ readable_list_of(expecteds).lstrip
440
+ end
441
+
442
+ # Override to use readable_list_of
443
+ def expected_for_failure_message
444
+ # TODO: Switch to using @divergent_items and handle this in the text
445
+ # builder
446
+ readable_list_of(@divergent_items).lstrip
447
+ end
448
+
449
+ # Update to use (...) as delimiter instead of {...}
450
+ def readable_list_of(items)
451
+ if items && items.all? { |item| item.is_a?(Hash) }
452
+ description_of(items.inject(:merge)).
453
+ sub(/^\{ /, '(').
454
+ sub(/ \}$/, ')')
455
+ else
456
+ super
457
+ end
458
+ end
459
+ end)
460
+ end
461
+
462
+ class Match
463
+ prepend SuperDiff::RSpec::AugmentedMatcher
464
+
465
+ prepend(Module.new do
466
+ def matcher_text_builder_class
467
+ SuperDiff::RSpec::MatcherTextBuilders::Match
468
+ end
469
+
470
+ def matcher_text_builder_args
471
+ super.merge(expected_captures: @expected_captures)
472
+ end
473
+ end)
474
+ end
475
+
476
+ class HaveAttributes
477
+ prepend SuperDiff::RSpec::AugmentedMatcher
478
+
479
+ prepend(Module.new do
480
+ # Use the message in the base matcher
481
+ def failure_message
482
+ respond_to_failure_message_or { super }
483
+ end
484
+
485
+ # Use the message in the base matcher
486
+ def failure_message_when_negated
487
+ respond_to_failure_message_or { super }
488
+ end
489
+
490
+ # Override to use the whole object, not just part of it
491
+ def actual_for_matcher_text
492
+ description_of(@actual)
493
+ end
494
+
495
+ # Override to use (...) as delimiters rather than {...}
496
+ def expected_for_matcher_text
497
+ super.sub(/^\{ /, '(').gsub(/ \}$/, ')')
498
+ end
499
+
500
+ # Override so that the differ knows that this is a partial object
501
+ def actual_for_diff
502
+ @actual
503
+ end
504
+
505
+ # Override so that the differ knows that this is a partial object
506
+ def expected_for_diff
507
+ if respond_to_failed
508
+ matchers.an_object_having_attributes(
509
+ @expected.select { |k, v| !@actual.respond_to?(k) }
510
+ )
511
+ else
512
+ matchers.an_object_having_attributes(@expected)
513
+ end
514
+ end
515
+ end)
516
+
517
+ # Override to force @values to get populated so that we can show a
518
+ # proper diff
519
+ def respond_to_attributes?
520
+ cache_all_values
521
+ matches = respond_to_matcher.matches?(@actual)
522
+ @respond_to_failed = !matches
523
+ matches
524
+ end
525
+
526
+ # Override this method to skip non-existent attributes, and to use
527
+ # public_send
528
+ def cache_all_values
529
+ @values = @expected.keys.inject({}) do |hash, attribute_key|
530
+ if @actual.respond_to?(attribute_key)
531
+ actual_value = @actual.public_send(attribute_key)
532
+ hash.merge(attribute_key => actual_value)
533
+ else
534
+ hash
535
+ end
536
+ end
537
+ end
538
+
539
+ def actual_has_attribute?(attribute_key, attribute_value)
540
+ values_match?(attribute_value, @values.fetch(attribute_key))
541
+ end
542
+
543
+ # Override to not improve_hash_formatting
544
+ def respond_to_failure_message_or
545
+ if respond_to_failed
546
+ respond_to_matcher.failure_message
547
+ else
548
+ yield
549
+ end
550
+ end
551
+ end
117
552
 
118
- def implementation
119
- RSpec::Core::Formatters::SyntaxHighlighter::NoSyntaxHighlightingImplementation
553
+ class RespondTo
554
+ prepend SuperDiff::RSpec::AugmentedMatcher
555
+
556
+ prepend(Module.new do
557
+ def matcher_text_builder_class
558
+ SuperDiff::RSpec::MatcherTextBuilders::RespondTo
559
+ end
560
+
561
+ def matcher_text_builder_args
562
+ super.merge(
563
+ expected_arity: @expected_arity,
564
+ arbitrary_keywords: @arbitrary_keywords,
565
+ expected_keywords: @expected_keywords,
566
+ unlimited_arguments: @unlimited_arguments
567
+ )
568
+ end
569
+
570
+ def expected_for_description
571
+ @names
572
+ end
573
+
574
+ def expected_for_failure_message
575
+ @failing_method_names
576
+ end
577
+ end)
578
+ end
579
+
580
+ class RaiseError
581
+ prepend SuperDiff::RSpec::AugmentedMatcher
582
+
583
+ prepend(Module.new do
584
+ def actual_for_matcher_text
585
+ if @actual_error
586
+ "#<#{@actual_error.class.name} #{@actual_error.message.inspect}>"
587
+ end
588
+ end
589
+
590
+ def actual_for_diff
591
+ @actual_error.message
592
+ end
593
+
594
+ def expected_for_matcher_text
595
+ if @expected_message
596
+ "#<#{@expected_error.name} #{@expected_message.inspect}>"
597
+ else
598
+ "#<#{@expected_error.name}>"
599
+ end
600
+ end
601
+
602
+ def expected_for_diff
603
+ @expected_message
604
+ end
605
+
606
+ def diffable?
607
+ !@expected_message.to_s.empty?
608
+ end
609
+
610
+ def expected_action_for_failure_message
611
+ "match"
612
+ end
613
+
614
+ def matcher_text_builder_class
615
+ SuperDiff::RSpec::MatcherTextBuilders::RaiseError
616
+ end
617
+ end)
618
+
619
+ def self.matcher_name
620
+ 'raise error'
621
+ end
622
+ end
623
+ end
120
624
  end
121
625
  end
122
- # rubocop:enable all