super_diff 0.1.0 → 0.2.0

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.
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