minispec 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.pryrc +2 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +22 -0
  5. data/README.md +2140 -0
  6. data/Rakefile +11 -0
  7. data/bin/minispec +4 -0
  8. data/lib/minispec.rb +175 -0
  9. data/lib/minispec/api.rb +2 -0
  10. data/lib/minispec/api/class.rb +195 -0
  11. data/lib/minispec/api/class/after.rb +49 -0
  12. data/lib/minispec/api/class/around.rb +54 -0
  13. data/lib/minispec/api/class/before.rb +101 -0
  14. data/lib/minispec/api/class/helpers.rb +116 -0
  15. data/lib/minispec/api/class/let.rb +44 -0
  16. data/lib/minispec/api/class/tests.rb +33 -0
  17. data/lib/minispec/api/instance.rb +158 -0
  18. data/lib/minispec/api/instance/mocks/doubles.rb +36 -0
  19. data/lib/minispec/api/instance/mocks/mocks.rb +319 -0
  20. data/lib/minispec/api/instance/mocks/spies.rb +17 -0
  21. data/lib/minispec/api/instance/mocks/stubs.rb +105 -0
  22. data/lib/minispec/helpers.rb +1 -0
  23. data/lib/minispec/helpers/array.rb +56 -0
  24. data/lib/minispec/helpers/booleans.rb +108 -0
  25. data/lib/minispec/helpers/generic.rb +24 -0
  26. data/lib/minispec/helpers/mocks/expectations.rb +29 -0
  27. data/lib/minispec/helpers/mocks/spies.rb +36 -0
  28. data/lib/minispec/helpers/raise.rb +44 -0
  29. data/lib/minispec/helpers/throw.rb +29 -0
  30. data/lib/minispec/mocks.rb +11 -0
  31. data/lib/minispec/mocks/expectations.rb +77 -0
  32. data/lib/minispec/mocks/stubs.rb +178 -0
  33. data/lib/minispec/mocks/validations.rb +80 -0
  34. data/lib/minispec/mocks/validations/amount.rb +63 -0
  35. data/lib/minispec/mocks/validations/arguments.rb +161 -0
  36. data/lib/minispec/mocks/validations/caller.rb +43 -0
  37. data/lib/minispec/mocks/validations/order.rb +47 -0
  38. data/lib/minispec/mocks/validations/raise.rb +111 -0
  39. data/lib/minispec/mocks/validations/return.rb +74 -0
  40. data/lib/minispec/mocks/validations/throw.rb +91 -0
  41. data/lib/minispec/mocks/validations/yield.rb +141 -0
  42. data/lib/minispec/proxy.rb +201 -0
  43. data/lib/minispec/reporter.rb +185 -0
  44. data/lib/minispec/utils.rb +139 -0
  45. data/lib/minispec/utils/differ.rb +325 -0
  46. data/lib/minispec/utils/pretty_print.rb +51 -0
  47. data/lib/minispec/utils/raise.rb +123 -0
  48. data/lib/minispec/utils/throw.rb +140 -0
  49. data/minispec.gemspec +27 -0
  50. data/test/mocks/expectations/amount.rb +67 -0
  51. data/test/mocks/expectations/arguments.rb +126 -0
  52. data/test/mocks/expectations/caller.rb +55 -0
  53. data/test/mocks/expectations/generic.rb +35 -0
  54. data/test/mocks/expectations/order.rb +46 -0
  55. data/test/mocks/expectations/raise.rb +166 -0
  56. data/test/mocks/expectations/return.rb +71 -0
  57. data/test/mocks/expectations/throw.rb +113 -0
  58. data/test/mocks/expectations/yield.rb +109 -0
  59. data/test/mocks/spies/amount.rb +68 -0
  60. data/test/mocks/spies/arguments.rb +57 -0
  61. data/test/mocks/spies/generic.rb +61 -0
  62. data/test/mocks/spies/order.rb +38 -0
  63. data/test/mocks/spies/raise.rb +158 -0
  64. data/test/mocks/spies/return.rb +71 -0
  65. data/test/mocks/spies/throw.rb +113 -0
  66. data/test/mocks/spies/yield.rb +101 -0
  67. data/test/mocks/test__doubles.rb +98 -0
  68. data/test/mocks/test__expectations.rb +27 -0
  69. data/test/mocks/test__mocks.rb +197 -0
  70. data/test/mocks/test__proxies.rb +61 -0
  71. data/test/mocks/test__spies.rb +43 -0
  72. data/test/mocks/test__stubs.rb +427 -0
  73. data/test/proxified_asserts.rb +34 -0
  74. data/test/setup.rb +53 -0
  75. data/test/test__around.rb +58 -0
  76. data/test/test__assert.rb +510 -0
  77. data/test/test__before_and_after.rb +117 -0
  78. data/test/test__before_and_after_all.rb +71 -0
  79. data/test/test__helpers.rb +197 -0
  80. data/test/test__raise.rb +104 -0
  81. data/test/test__skip.rb +41 -0
  82. data/test/test__throw.rb +103 -0
  83. metadata +196 -0
@@ -0,0 +1,185 @@
1
+ module MiniSpec
2
+ class Reporter
3
+ @@indent = " ".freeze
4
+
5
+ attr_reader :failed_specs, :failed_tests, :skipped_tests
6
+
7
+ def initialize stdout = STDOUT
8
+ @stdout = stdout
9
+ @failed_specs, @failed_tests, @skipped_tests = [], {}, {}
10
+ end
11
+
12
+ def summary
13
+ summary__failed_specs
14
+ summary__failed_tests
15
+ summary__skipped_tests
16
+ totals
17
+ end
18
+
19
+ def summary__failed_specs
20
+ return if @failed_specs.empty?
21
+ puts
22
+ puts(error('--- Failed Specs ---'))
23
+ last_ex = nil
24
+ @failed_specs.each do |(spec,proc,ex)|
25
+ puts(info(spec))
26
+ puts(info('defined at ' + proc.source_location.join(':')), indent: 2) if proc.is_a?(Proc)
27
+ if last_ex && ex.backtrace == last_ex.backtrace
28
+ puts('see exception above', indent: 2)
29
+ next
30
+ end
31
+ last_ex = ex
32
+ puts(error(ex.message), indent: 2)
33
+ ex.backtrace.each {|l| puts(l, indent: 2)}
34
+ puts
35
+ end
36
+ end
37
+
38
+ def summary__skipped_tests
39
+ return if @skipped_tests.empty?
40
+ puts
41
+ puts(warn('--- Skipped Tests ---'))
42
+ @skipped_tests.each_pair do |spec,tests|
43
+ puts(info(spec))
44
+ tests.each do |(test,source_location)|
45
+ puts(warn(test), indent: 2)
46
+ puts(info(MiniSpec::Utils.shorten_source(source_location)), indent: 2)
47
+ puts
48
+ end
49
+ puts
50
+ end
51
+ end
52
+
53
+ def summary__failed_tests
54
+ return if @failed_tests.empty?
55
+ puts
56
+ puts(error('--- Failed Tests ---'), '')
57
+ @failed_tests.each_pair do |spec, failures|
58
+ @failed_specs.push(spec) # to be used on #totals__specs
59
+ failures.each do |(test,verb,proc,errors)|
60
+ errors.each do |error|
61
+ error.is_a?(Exception) ?
62
+ exception_details(spec, test, error) :
63
+ failure_details(spec, test, error)
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ def totals
70
+ puts
71
+ puts('---')
72
+ totals__specs
73
+ totals__tests
74
+ totals__assertions
75
+ end
76
+
77
+ def totals__specs
78
+ print(info(' Specs: '))
79
+ return puts(success(Minispec.specs.size)) if @failed_specs.empty?
80
+ print(info(Minispec.specs.size))
81
+ puts(error(' (%s failed)' % @failed_specs.size))
82
+ end
83
+
84
+ def totals__tests
85
+ print(info(' Tests: '))
86
+ print(send(@failed_tests.any? ? :info : :success, Minispec.tests))
87
+ failed = error('%s failed' % @failed_tests.values.map(&:size).reduce(:+)) if @failed_tests.any?
88
+ skipped = warn('%s skipped' % @skipped_tests.size) if @skipped_tests.any?
89
+ report = [failed, skipped].compact.join(', ')
90
+ puts(report.empty? ? report : ' (%s)' % report)
91
+ end
92
+
93
+ def totals__assertions
94
+ print(info(' Assertions: '))
95
+ return puts(success(Minispec.assertions)) if @failed_tests.empty?
96
+ print(info(Minispec.assertions))
97
+ puts(error(' (%s failed)' % @failed_tests.values.map(&:size).reduce(:+)))
98
+ end
99
+
100
+ def exception_details spec, test, exception
101
+ puts(info([spec, test]*' / '))
102
+ puts(error(exception.message), indent: 2)
103
+ exception.backtrace.each {|l| puts(info(MiniSpec::Utils.shorten_source(l)), indent: 2)}
104
+ puts('---', '')
105
+ end
106
+
107
+ def failure_details spec, test, failure
108
+ puts(info([spec, test]*' / '))
109
+ puts(error(callerline(failure[:callers][0])), indent: 2)
110
+ callers(failure[:callers]).each {|l| puts(info(l), indent: 2)}
111
+ puts
112
+ return puts(*failure[:message].split("\n"), '', indent: 2) if failure[:message]
113
+ return if failure[:right_object] == :__ms__right_object
114
+
115
+ expected, actual = [:right_object, :left_object].map do |obj|
116
+ str = stringify_object(failure[obj])
117
+ [str =~ /\n/ ? :puts : :print, str]
118
+ end
119
+
120
+ send(expected.first, info(' Expected: '))
121
+ print('NOT ') if failure[:negation]
122
+ puts(expected.last)
123
+
124
+ send(actual.first, info(' Actual: '))
125
+ puts(actual.last)
126
+
127
+ print(info(' Compared using: '))
128
+ puts(failure[:right_method])
129
+
130
+ diff = diff(actual.last, expected.last)
131
+ puts(info(' Diff: '), diff) unless diff.empty?
132
+ puts('---', '')
133
+ end
134
+
135
+ def mark_as_passed spec, test
136
+ puts(success("OK"))
137
+ end
138
+
139
+ def mark_as_skipped spec, test, source_location
140
+ puts(warn("Skipped"))
141
+ (@skipped_tests[spec] ||= []).push([test, source_location])
142
+ end
143
+
144
+ def mark_as_failed spec, test, verb, proc, failures
145
+ puts(error("FAILED"))
146
+ (@failed_tests[spec] ||= []).push([test, verb, proc, failures])
147
+ end
148
+
149
+ def print(*args); @stdout.print(*indent_lines(*args)) end
150
+ def puts(*args); @stdout.puts(*indent_lines(*args)) end
151
+
152
+ {success: 32, info: 34, warn: 35, error: 31}.each_pair do |m,c|
153
+ define_method(m) {|s| "\e[%im%s\e[0m" % [c, s]}
154
+ end
155
+
156
+ def failures?
157
+ @failed_specs.any? || @failed_tests.any?
158
+ end
159
+
160
+ private
161
+ def indent_lines *args
162
+ opts = args.last.is_a?(Hash) ? args.pop : {}
163
+ (i = opts[:indent]) && (i = @@indent*i) && args.map {|l| i + l} || args
164
+ end
165
+
166
+ def callers *callers
167
+ callers.flatten.uniq.reverse.map {|l| MiniSpec::Utils.shorten_source(l)}
168
+ end
169
+
170
+ def callerline caller
171
+ file, line = caller.match(/^(.+?):(\d+)(?::in `(.*)')?/) {|m| m[1..2]}
172
+ return unless lines = MiniSpec.source_location_cache(file)
173
+ (line = lines[line.to_i - 1]) && line.strip
174
+ end
175
+
176
+ def stringify_object obj
177
+ obj.is_a?(String) ? obj : MiniSpec::Utils.pp(obj)
178
+ end
179
+
180
+ def diff actual, expected
181
+ @differ ||= MiniSpec::Differ.new(color: true)
182
+ @differ.diff(actual, expected).strip
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,139 @@
1
+ module MiniSpec
2
+ module Utils
3
+ extend self
4
+
5
+ def undefine_method object, method
6
+ return unless method_defined?(object, method)
7
+ object.instance_eval('undef :%s' % method)
8
+ end
9
+
10
+ def method_defined? object, method
11
+ object.respond_to?(method) ||
12
+ object.protected_methods.include?(method) ||
13
+ object.private_methods.include?(method)
14
+ end
15
+
16
+ # @api private
17
+ # checking whether correct arguments passed to proxy methods.
18
+ #
19
+ # @raise [ArgumentError] if more than two arguments given
20
+ # @raise [ArgumentError] if both argument and block given
21
+ #
22
+ def valid_proxy_arguments? left_method, *args, &proc
23
+ args.size > 2 && raise(ArgumentError, '#%s - wrong number of arguments, %i for 0..2' % [left_method, args.size])
24
+ args.size > 0 && proc && raise(ArgumentError, '#%s accepts either arguments or a block, not both' % left_method)
25
+ end
26
+
27
+ # @example
28
+ # received = [:a, :b, :c]
29
+ # expected = [1]
30
+ # => {:a=>1, :b=>1, :c=>1}
31
+ #
32
+ # @example
33
+ # received = [:a, :b, :c]
34
+ # expected = []
35
+ # => {:a=>nil, :b=>nil, :c=>nil}
36
+ #
37
+ # @example
38
+ # received = [:a, :b, :c]
39
+ # expected = [1, 2]
40
+ # => {:a=>1, :b=>2, :c=>2}
41
+ #
42
+ # @example
43
+ # received = [:a, :b, :c]
44
+ # expected = [1, 2, 3, 4]
45
+ # => {:a=>1, :b=>2, :c=>3}
46
+ #
47
+ # @param received [Array]
48
+ # @param expected [Array]
49
+ # @return [Hash]
50
+ #
51
+ def zipper received, expected
52
+ result = {}
53
+ received.uniq.each_with_index do |m,i|
54
+ result[m] = expected[i] || expected[i-1] || expected[0]
55
+ end
56
+ result
57
+ end
58
+
59
+ # determines method's visibility
60
+ #
61
+ # @param object
62
+ # @param method
63
+ # @return [Symbol] or nil
64
+ #
65
+ def method_visibility object, method
66
+ {
67
+ public: :public_methods,
68
+ protected: :protected_methods,
69
+ private: :private_methods
70
+ }.each_pair do |v,m|
71
+ return v if object.send(m).include?(method)
72
+ end
73
+ nil
74
+ end
75
+
76
+ def array_elements_map array
77
+ # borrowed from thoughtbot/shoulda
78
+ array.inject({}) {|h,e| h[e] ||= array.select { |i| i == e }.size; h}
79
+ end
80
+
81
+ def source proc
82
+ shorten_source(proc.source_location*':')
83
+ end
84
+
85
+ # get rid of Dir.pwd from given path
86
+ def shorten_source source
87
+ source.to_s.sub(/\A#{Dir.pwd}\/?/, '')
88
+ end
89
+
90
+ # checks whether given label matches any matcher.
91
+ # even if label matched, it will return `false` if label matches some rejector.
92
+ #
93
+ # @param label
94
+ # @param matchers an `Array` of matchers and rejectors.
95
+ # matchers contained as hashes, rejectors as arrays.
96
+ # @return `true` or `false`
97
+ #
98
+ def any_match? label, matchers
99
+ reject, select = matchers.partition {|m| m.is_a?(Hash)}
100
+
101
+ rejected = rejected?(label, reject)
102
+ if select.any?
103
+ return select.find {|x| (x == :*) || match?(label, x)} && !rejected
104
+ end
105
+ !rejected
106
+ end
107
+
108
+ # checks whether given label matches any rejector.
109
+ #
110
+ # @param label
111
+ # @param reject an `Array` of rejectors, each being a `Hash` containing `:except` key
112
+ # @return `true` or `false`
113
+ #
114
+ def rejected? label, reject
115
+ if reject.any? && (x = reject.first[:except])
116
+ if x.is_a?(Array)
117
+ return true if x.find {|m| match?(label, m)}
118
+ else
119
+ return true if match?(label, x)
120
+ end
121
+ end
122
+ false
123
+ end
124
+
125
+ # compare given label to given expression.
126
+ # if expression is a `Regexp` comparing using `=~`.
127
+ # otherwise `==` are used
128
+ #
129
+ # @param label
130
+ # @param x
131
+ # @return `true` or `false`
132
+ #
133
+ def match? label, x
134
+ x.is_a?(Regexp) ? label.to_s =~ x : label == x
135
+ end
136
+ end
137
+ end
138
+
139
+ Dir[File.expand_path('../utils/**/*.rb', __FILE__)].each {|f| require(f)}
@@ -0,0 +1,325 @@
1
+ # borrowed from RSpec - https://github.com/rspec/rspec-support
2
+
3
+ # Copyright (c) 2013 David Chelimsky, Myron Marston, Jon Rowe, Sam Phippen, Xavier Shay, Bradley Schaefer
4
+ #
5
+ # MIT License
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ module MiniSpec
27
+ class Differ
28
+ class EncodedString
29
+
30
+ MRI_UNICODE_UNKNOWN_CHARACTER = "\xEF\xBF\xBD".freeze
31
+
32
+ def initialize(string, encoding = nil)
33
+ @encoding = encoding
34
+ @source_encoding = detect_source_encoding(string)
35
+ @string = matching_encoding(string)
36
+ end
37
+ attr_reader :source_encoding
38
+
39
+ delegated_methods = String.instance_methods.map(&:to_s) & %w[eql? lines == encoding empty?]
40
+ delegated_methods.each do |name|
41
+ define_method(name) { |*args, &block| @string.__send__(name, *args, &block) }
42
+ end
43
+
44
+ def <<(string)
45
+ @string << matching_encoding(string)
46
+ end
47
+
48
+ def split(regex_or_string)
49
+ @string.split(matching_encoding(regex_or_string))
50
+ end
51
+
52
+ def to_s
53
+ @string
54
+ end
55
+ alias :to_str :to_s
56
+
57
+ private
58
+
59
+ if String.method_defined?(:encoding)
60
+ def matching_encoding(string)
61
+ string.encode(@encoding)
62
+ rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
63
+ normalize_missing(string.encode(@encoding, :invalid => :replace, :undef => :replace))
64
+ rescue Encoding::ConverterNotFoundError
65
+ normalize_missing(string.force_encoding(@encoding).encode(:invalid => :replace))
66
+ end
67
+
68
+ def normalize_missing(string)
69
+ if @encoding.to_s == "UTF-8"
70
+ string.gsub(MRI_UNICODE_UNKNOWN_CHARACTER.force_encoding(@encoding), "?")
71
+ else
72
+ string
73
+ end
74
+ end
75
+
76
+ def detect_source_encoding(string)
77
+ string.encoding
78
+ end
79
+ else
80
+ def matching_encoding(string)
81
+ string
82
+ end
83
+
84
+ def detect_source_encoding(string)
85
+ 'US-ASCII'
86
+ end
87
+ end
88
+ end
89
+
90
+ class HunkGenerator
91
+ def initialize(actual, expected)
92
+ @actual = actual
93
+ @expected = expected
94
+ end
95
+
96
+ def hunks
97
+ @file_length_difference = 0
98
+ @hunks ||= diffs.map do |piece|
99
+ build_hunk(piece)
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ def diffs
106
+ Diff::LCS.diff(expected_lines, actual_lines)
107
+ end
108
+
109
+ def expected_lines
110
+ @expected.split("\n").map! { |e| e.chomp }
111
+ end
112
+
113
+ def actual_lines
114
+ @actual.split("\n").map! { |e| e.chomp }
115
+ end
116
+
117
+ def build_hunk(piece)
118
+ Diff::LCS::Hunk.new(
119
+ expected_lines, actual_lines, piece, context_lines, @file_length_difference
120
+ ).tap do |h|
121
+ @file_length_difference = h.file_length_difference
122
+ end
123
+ end
124
+
125
+ def context_lines
126
+ 3
127
+ end
128
+ end
129
+
130
+ def diff(actual, expected)
131
+ diff = ""
132
+
133
+ if actual && expected
134
+ if all_strings?(actual, expected)
135
+ if any_multiline_strings?(actual, expected)
136
+ diff = diff_as_string(coerce_to_string(actual), coerce_to_string(expected))
137
+ end
138
+ elsif no_procs?(actual, expected) && no_numbers?(actual, expected)
139
+ diff = diff_as_object(actual, expected)
140
+ end
141
+ end
142
+
143
+ diff.to_s
144
+ end
145
+
146
+ def diff_as_string(actual, expected)
147
+ @encoding = pick_encoding actual, expected
148
+
149
+ @actual = EncodedString.new(actual, @encoding)
150
+ @expected = EncodedString.new(expected, @encoding)
151
+
152
+ output = EncodedString.new("\n", @encoding)
153
+
154
+ hunks.each_cons(2) do |prev_hunk, current_hunk|
155
+ begin
156
+ if current_hunk.overlaps?(prev_hunk)
157
+ add_old_hunk_to_hunk(current_hunk, prev_hunk)
158
+ else
159
+ add_to_output(output, prev_hunk.diff(format).to_s)
160
+ end
161
+ ensure
162
+ add_to_output(output, "\n")
163
+ end
164
+ end
165
+
166
+ if hunks.last
167
+ finalize_output(output, hunks.last.diff(format).to_s)
168
+ end
169
+
170
+ color_diff output
171
+ rescue Encoding::CompatibilityError
172
+ handle_encoding_errors
173
+ end
174
+
175
+ def diff_as_object(actual, expected)
176
+ actual_as_string = object_to_string(actual)
177
+ expected_as_string = object_to_string(expected)
178
+ diff_as_string(actual_as_string, expected_as_string)
179
+ end
180
+
181
+ attr_reader :color
182
+ alias_method :color?, :color
183
+
184
+ def initialize(opts={})
185
+ @color = opts.fetch(:color, false)
186
+ @object_preparer = opts.fetch(:object_preparer, lambda { |string| string })
187
+ end
188
+
189
+ private
190
+
191
+ def no_procs?(*args)
192
+ args.flatten.none? { |a| Proc === a}
193
+ end
194
+
195
+ def all_strings?(*args)
196
+ args.flatten.all? { |a| String === a}
197
+ end
198
+
199
+ def any_multiline_strings?(*args)
200
+ all_strings?(*args) && args.flatten.any? { |a| multiline?(a) }
201
+ end
202
+
203
+ def no_numbers?(*args)
204
+ args.flatten.none? { |a| Numeric === a}
205
+ end
206
+
207
+ def coerce_to_string(string_or_array)
208
+ return string_or_array unless Array === string_or_array
209
+ diffably_stringify(string_or_array).join("\n")
210
+ end
211
+
212
+ def diffably_stringify(array)
213
+ array.map do |entry|
214
+ if Array === entry
215
+ entry.inspect
216
+ else
217
+ entry.to_s.gsub("\n", "\\n")
218
+ end
219
+ end
220
+ end
221
+
222
+ if String.method_defined?(:encoding)
223
+ def multiline?(string)
224
+ string.include?("\n".encode(string.encoding))
225
+ end
226
+ else
227
+ def multiline?(string)
228
+ string.include?("\n")
229
+ end
230
+ end
231
+
232
+ def hunks
233
+ @hunks ||= HunkGenerator.new(@actual, @expected).hunks
234
+ end
235
+
236
+ def finalize_output(output, final_line)
237
+ add_to_output(output, final_line)
238
+ add_to_output(output, "\n")
239
+ end
240
+
241
+ def add_to_output(output, string)
242
+ output << string
243
+ end
244
+
245
+ def add_old_hunk_to_hunk(hunk, oldhunk)
246
+ hunk.merge(oldhunk)
247
+ end
248
+
249
+ def format
250
+ :unified
251
+ end
252
+
253
+ def color(text, color_code)
254
+ "\e[#{color_code}m#{text}\e[0m"
255
+ end
256
+
257
+ def red(text)
258
+ color(text, 31)
259
+ end
260
+
261
+ def green(text)
262
+ color(text, 32)
263
+ end
264
+
265
+ def blue(text)
266
+ color(text, 34)
267
+ end
268
+
269
+ def normal(text)
270
+ color(text, 0)
271
+ end
272
+
273
+ def color_diff(diff)
274
+ return diff unless color?
275
+
276
+ diff.lines.map { |line|
277
+ case line[0].chr
278
+ when "+"
279
+ green line
280
+ when "-"
281
+ red line
282
+ when "@"
283
+ line[1].chr == "@" ? blue(line) : normal(line)
284
+ else
285
+ normal(line)
286
+ end
287
+ }.join
288
+ end
289
+
290
+ def object_to_string(object)
291
+ object = @object_preparer.call(object)
292
+ case object
293
+ when Hash
294
+ object.keys.sort_by { |k| k.to_s }.map do |key|
295
+ pp_key = PP.singleline_pp(key, "")
296
+ pp_value = PP.singleline_pp(object[key], "")
297
+
298
+ "#{pp_key} => #{pp_value},"
299
+ end.join("\n")
300
+ when String
301
+ object =~ /\n/ ? object : object.inspect
302
+ else
303
+ PP.pp(object,"")
304
+ end
305
+ end
306
+
307
+ if String.method_defined?(:encoding)
308
+ def pick_encoding(source_a, source_b)
309
+ Encoding.compatible?(source_a, source_b) || Encoding.default_external
310
+ end
311
+ else
312
+ def pick_encoding(source_a, source_b)
313
+ end
314
+ end
315
+
316
+ def handle_encoding_errors
317
+ if @actual.source_encoding != @expected.source_encoding
318
+ "Could not produce a diff because the encoding of the actual string (#{@actual.source_encoding}) "+
319
+ "differs from the encoding of the expected string (#{@expected.source_encoding})"
320
+ else
321
+ "Could not produce a diff because of the encoding of the string (#{@expected.source_encoding})"
322
+ end
323
+ end
324
+ end
325
+ end