Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow inspectors to be registered for different objects #516

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion lib/rspec/support/object_formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class ObjectFormatter # rubocop:disable Metrics/ClassLength
ELLIPSIS = "..."

attr_accessor :max_formatted_output_length
attr_reader :inspectors

# Methods are deferred to a default instance of the class to maintain the interface
# For example, calling ObjectFormatter.format is still possible
Expand All @@ -24,9 +25,14 @@ def self.prepare_for_inspection(object)
default_instance.prepare_for_inspection(object)
end

def self.inspectors
default_instance.inspectors
end

def initialize(max_formatted_output_length=200)
@max_formatted_output_length = max_formatted_output_length
@current_structure_stack = []
@inspectors = Inspectors.new(INSPECTOR_CLASSES.dup)
end

def format(object)
Expand Down Expand Up @@ -60,7 +66,7 @@ def prepare_for_inspection(object)
when Hash
prepare_hash(object)
else
inspector_class = INSPECTOR_CLASSES.find { |inspector| inspector.can_inspect?(object) }
inspector_class = inspectors.find_for(object)
inspector_class.new(object, self)
end
end
Expand Down Expand Up @@ -257,6 +263,27 @@ def inspect
classes.delete(BigDecimalInspector) if RUBY_VERSION >= '2.4'
end

# A simple container for inspector classes, so that inspector classes can be registered.
# Inspectors registered later will take precedence over previously registered ones.
# @api private
class Inspectors
def initialize(inspectors)
@inspectors = inspectors
end

def register(inspector)
unless inspector.respond_to?(:can_inspect?)
raise ArgumentError, 'provided inspector does not respond to can_inspect?'
end

@inspectors.unshift(inspector)
end

def find_for(object)
@inspectors.find { |inspector| inspector.can_inspect?(object) }
end
end

private

# Returns the substring defined by the start_index and end_index
Expand Down
58 changes: 58 additions & 0 deletions spec/rspec/support/object_formatter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,64 @@ def inspect
expect(formatter.format('Test String Of A Longer Length')).to eq('"Test String Of A Longer Length"')
end
end

context 'with a custom registered inspector' do
class TimeObjectTest
SECONDS_PER_MINUTE = 60

def initialize(minutes, seconds)
@minutes = minutes
@seconds = seconds
end

def to_i
(@minutes * SECONDS_PER_MINUTE) + @seconds
end

def inspect
"#{@minutes} minutes and #{@seconds} seconds"
end

def ==(other)
self.to_i == other.to_i
end

alias_method :eql?, :==

def hash
[@minutes, @seconds].hash
end
end

class CustomInspector < ObjectFormatter::BaseInspector
def self.can_inspect?(object)
TimeObjectTest === object
end

def inspect
object.to_i.inspect
end
end

before do
ObjectFormatter.inspectors.register(CustomInspector)
end

subject(:output) do
ObjectFormatter.format(input)
end

let(:input) do
[
{ :key => TimeObjectTest.new(1, 30) },
{ :key => TimeObjectTest.new(0, 90) }
]
end

it "uses the custom inspector" do
expect(output).to eq('[{:key=>90}, {:key=>90}]')
end
end
end
end
end