Skip to content

Commit 196991a

Browse files
committed
Add integration with RSpec
Right now, RSpec doesn't support replacing its differ with a custom implementation. Therefore, we have to resort to monkey-patching some internal RSpec classes to use SuperDiff instead. While we're at it, disable some of the coloring that RSpec does, particularly its habit of styling failures in blood red. We want to be able to show off the diff and all of its glory and this just gets in the way.
1 parent 8f47e11 commit 196991a

File tree

15 files changed

+829
-69
lines changed

15 files changed

+829
-69
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
.DS_Store
22
spec/examples.txt
3+
tmp

lib/super_diff.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@
4343

4444
require_relative "super_diff/differs/base"
4545
require_relative "super_diff/differs/array"
46+
require_relative "super_diff/differs/empty"
4647
require_relative "super_diff/differs/hash"
47-
require_relative "super_diff/differs/string"
48+
require_relative "super_diff/differs/multi_line_string"
4849
require_relative "super_diff/differs/object"
4950
require_relative "super_diff/differs"
5051
require_relative "super_diff/differ"

lib/super_diff/differ.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def self.call(*args)
77
def initialize(
88
expected,
99
actual,
10-
indent_level:,
10+
indent_level: 0,
1111
index_in_collection: nil,
1212
extra_classes: [],
1313
extra_operational_sequencer_classes: [],
@@ -35,6 +35,10 @@ def call
3535

3636
private
3737

38+
attr_reader :expected, :actual, :indent_level, :index_in_collection,
39+
:extra_classes, :extra_operational_sequencer_classes,
40+
:extra_diff_formatter_classes
41+
3842
def resolved_class
3943
(Differs::DEFAULTS + extra_classes).detect do |klass|
4044
klass.applies_to?(expected) && klass.applies_to?(actual)

lib/super_diff/differs.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module SuperDiff
22
module Differs
3-
DEFAULTS = [Array, Hash, String, Object]
3+
DEFAULTS = [Array, Hash, MultiLineString, Object, Empty]
44
end
55
end

lib/super_diff/differs/empty.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module SuperDiff
2+
module Differs
3+
class Empty < Base
4+
def self.applies_to?(value)
5+
true
6+
end
7+
8+
def call
9+
""
10+
end
11+
end
12+
end
13+
end
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module SuperDiff
2+
module Differs
3+
class MultiLineString < Base
4+
def self.applies_to?(value)
5+
value.is_a?(::String) && value.include?("\n")
6+
end
7+
8+
def call
9+
DiffFormatters::MultiLineString.call(
10+
operations,
11+
indent_level: indent_level
12+
)
13+
end
14+
15+
private
16+
17+
def operations
18+
OperationalSequencers::MultiLineString.call(
19+
expected: expected,
20+
actual: actual,
21+
extra_operational_sequencer_classes: extra_operational_sequencer_classes,
22+
extra_diff_formatter_classes: extra_diff_formatter_classes
23+
)
24+
end
25+
end
26+
end
27+
end

lib/super_diff/differs/string.rb

Lines changed: 0 additions & 53 deletions
This file was deleted.

lib/super_diff/equality_matchers/multi_line_string.rb

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,12 @@ def fail
3131
private
3232

3333
def diff
34-
DiffFormatters::MultiLineString.call(operations, indent_level: 0)
35-
end
36-
37-
def operations
38-
OperationalSequencers::MultiLineString.call(
39-
expected: expected,
40-
actual: actual
34+
Differs::MultiLineString.call(
35+
expected,
36+
actual,
37+
indent_level: 0,
38+
extra_operational_sequencer_classes: extra_operational_sequencer_classes,
39+
extra_diff_formatter_classes: extra_diff_formatter_classes
4140
)
4241
end
4342
end

lib/super_diff/rspec.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
require_relative "../super_diff"
2+
require_relative "rspec/differ"
3+
require_relative "rspec/monkey_patches"
4+
5+
module SuperDiff
6+
module RSpec
7+
class << self
8+
attr_accessor :extra_operational_sequencer_classes
9+
attr_accessor :extra_diff_formatter_classes
10+
11+
def configure
12+
yield self
13+
end
14+
end
15+
16+
self.extra_operational_sequencer_classes = []
17+
self.extra_diff_formatter_classes = []
18+
end
19+
end

lib/super_diff/rspec/differ.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module SuperDiff
2+
module RSpec
3+
module Differ
4+
def self.diff(actual, expected)
5+
diff = SuperDiff::Differ.call(
6+
expected,
7+
actual,
8+
extra_operational_sequencer_classes: RSpec.extra_operational_sequencer_classes,
9+
extra_diff_formatter_classes: RSpec.extra_diff_formatter_classes
10+
)
11+
"\n\n" + diff
12+
end
13+
end
14+
end
15+
end
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
RSpec::Expectations.instance_eval do
2+
def differ
3+
SuperDiff::RSpec::Differ
4+
end
5+
end
6+
7+
RSpec::Core::Formatters::ConsoleCodes.instance_eval do
8+
# UPDATE: Patch so it returns nothing if code_or_symbol is nil
9+
def console_code_for(code_or_symbol)
10+
if code_or_symbol
11+
if (config_method = config_colors_to_methods[code_or_symbol])
12+
console_code_for RSpec.configuration.__send__(config_method)
13+
elsif RSpec::Core::Formatters::ConsoleCodes::VT100_CODE_VALUES.key?(code_or_symbol)
14+
code_or_symbol
15+
else
16+
RSpec::Core::Formatters::ConsoleCodes::VT100_CODES.fetch(code_or_symbol) do
17+
console_code_for(:white)
18+
end
19+
end
20+
end
21+
end
22+
23+
# UPDATE: Patch so it does not apply a color if code_or_symbol is nil
24+
def wrap(text, code_or_symbol)
25+
if RSpec.configuration.color_enabled? && code = console_code_for(code_or_symbol)
26+
"\e[#{code}m#{text}\e[0m"
27+
else
28+
text
29+
end
30+
end
31+
end
32+
33+
RSpec::Core::Formatters::ExceptionPresenter.class_eval do
34+
# UPDATE: Copy from SyntaxHighlighter::CodeRayImplementation
35+
RESET_CODE = "\e[0m"
36+
37+
def initialize(exception, example, options={})
38+
@exception = exception
39+
@example = example
40+
# UPDATE: Use no color by default
41+
@message_color = options[:message_color]
42+
@description = options.fetch(:description) { example.full_description }
43+
@detail_formatter = options.fetch(:detail_formatter) { Proc.new {} }
44+
@extra_detail_formatter = options.fetch(:extra_detail_formatter) { Proc.new {} }
45+
@backtrace_formatter = options.fetch(:backtrace_formatter) { RSpec.configuration.backtrace_formatter }
46+
@indentation = options.fetch(:indentation, 2)
47+
@skip_shared_group_trace = options.fetch(:skip_shared_group_trace, false)
48+
@failure_lines = options[:failure_lines]
49+
end
50+
51+
def add_shared_group_lines(lines, colorizer)
52+
return lines if @skip_shared_group_trace
53+
54+
example.metadata[:shared_group_inclusion_backtrace].each do |frame|
55+
# Update: Use red instead of the default color
56+
lines << colorizer.wrap(frame.description, :failure)
57+
end
58+
59+
lines
60+
end
61+
62+
# UPDATE: Style the first part in blue and the snippet of the line that failed
63+
# in white
64+
def failure_slash_error_lines
65+
lines = read_failed_lines
66+
67+
failure_slash_error = RSpec::Core::Formatters::ConsoleCodes.wrap(
68+
"Failure/Error: ",
69+
:detail
70+
)
71+
72+
if lines.count == 1
73+
lines[0] =
74+
failure_slash_error +
75+
RSpec::Core::Formatters::ConsoleCodes.wrap(lines[0].strip, :white)
76+
else
77+
least_indentation = SnippetExtractor.least_indentation_from(lines)
78+
lines = lines.map do |line|
79+
RSpec::Core::Formatters::ConsoleCodes.wrap(
80+
line.sub(/^#{least_indentation}/, ' '),
81+
:white
82+
)
83+
end
84+
lines.unshift(failure_slash_error)
85+
end
86+
87+
lines
88+
end
89+
end
90+
91+
RSpec::Matchers::BuiltIn::Eq.class_eval do
92+
def failure_message
93+
"\n" +
94+
colorizer.wrap("expected: #{expected_formatted}\n", :failure) +
95+
colorizer.wrap(" got: #{actual_formatted}\n\n", :success) +
96+
colorizer.wrap("(compared using ==)\n", :detail)
97+
end
98+
99+
def failure_message_when_negated
100+
"\n" +
101+
colorizer.wrap("expected: value != #{expected_formatted}\n", :failure) +
102+
colorizer.wrap(" got: #{actual_formatted}\n\n", :success) +
103+
colorizer.wrap("(compared using ==)\n", :detail)
104+
end
105+
106+
private
107+
108+
def colorizer
109+
RSpec::Core::Formatters::ConsoleCodes
110+
end
111+
end
112+
113+
RSpec::Core::Formatters::SyntaxHighlighter.class_eval do
114+
private
115+
116+
def implementation
117+
RSpec::Core::Formatters::SyntaxHighlighter::NoSyntaxHighlightingImplementation
118+
end
119+
end

0 commit comments

Comments
 (0)