diff --git a/lib/super_diff/differs.rb b/lib/super_diff/differs.rb index 8edf4308..da12f71e 100644 --- a/lib/super_diff/differs.rb +++ b/lib/super_diff/differs.rb @@ -7,10 +7,12 @@ module Differs autoload :Empty, "super_diff/differs/empty" autoload :Hash, "super_diff/differs/hash" autoload :MultilineString, "super_diff/differs/multiline_string" + autoload :Time, "super_diff/differs/time" DEFAULTS = [ Array, Hash, + Time, MultilineString, CustomObject, DefaultObject, diff --git a/lib/super_diff/differs/time.rb b/lib/super_diff/differs/time.rb new file mode 100644 index 00000000..10f219b8 --- /dev/null +++ b/lib/super_diff/differs/time.rb @@ -0,0 +1,24 @@ +module SuperDiff + module Differs + class Time < Base + def self.applies_to?(expected, actual) + OperationalSequencers::TimeLike.applies_to?(expected, actual) + end + + def call + operations.to_diff(indent_level: indent_level) + end + + private + + def operations + OperationalSequencers::TimeLike.call( + expected: expected, + actual: actual, + extra_operational_sequencer_classes: extra_operational_sequencer_classes, + extra_diff_formatter_classes: extra_diff_formatter_classes, + ) + end + end + end +end diff --git a/lib/super_diff/object_inspection/inspectors.rb b/lib/super_diff/object_inspection/inspectors.rb index 90ad9e9a..1ac9b28a 100644 --- a/lib/super_diff/object_inspection/inspectors.rb +++ b/lib/super_diff/object_inspection/inspectors.rb @@ -13,6 +13,7 @@ module Inspectors autoload :Hash, "super_diff/object_inspection/inspectors/hash" autoload :Primitive, "super_diff/object_inspection/inspectors/primitive" autoload :String, "super_diff/object_inspection/inspectors/string" + autoload :Time, "super_diff/object_inspection/inspectors/time" end end end diff --git a/lib/super_diff/object_inspection/inspectors/time.rb b/lib/super_diff/object_inspection/inspectors/time.rb new file mode 100644 index 00000000..479a414b --- /dev/null +++ b/lib/super_diff/object_inspection/inspectors/time.rb @@ -0,0 +1,13 @@ +module SuperDiff + module ObjectInspection + module Inspectors + TIME_FORMAT = "%Y-%m-%d %H:%M:%S.%3N %Z %:z".freeze + + Time = InspectionTree.new do + add_text do |time| + "#{time.strftime(TIME_FORMAT)} (#{time.class})" + end + end + end + end +end diff --git a/lib/super_diff/object_inspection/map.rb b/lib/super_diff/object_inspection/map.rb index 99d166b4..9d032542 100644 --- a/lib/super_diff/object_inspection/map.rb +++ b/lib/super_diff/object_inspection/map.rb @@ -16,6 +16,8 @@ def call(object) Inspectors::Hash when String Inspectors::String + when Time + Inspectors::Time when true, false, nil, Symbol, Numeric, Regexp, Class Inspectors::Primitive else diff --git a/lib/super_diff/operational_sequencers.rb b/lib/super_diff/operational_sequencers.rb index 1f41372d..28902965 100644 --- a/lib/super_diff/operational_sequencers.rb +++ b/lib/super_diff/operational_sequencers.rb @@ -10,6 +10,7 @@ module OperationalSequencers :MultilineString, "super_diff/operational_sequencers/multiline_string", ) + autoload :TimeLike, "super_diff/operational_sequencers/time_like" DEFAULTS = [Array, Hash, CustomObject].freeze end diff --git a/lib/super_diff/operational_sequencers/time_like.rb b/lib/super_diff/operational_sequencers/time_like.rb new file mode 100644 index 00000000..081410bb --- /dev/null +++ b/lib/super_diff/operational_sequencers/time_like.rb @@ -0,0 +1,30 @@ +module SuperDiff + module OperationalSequencers + class TimeLike < CustomObject + def self.applies_to?(expected, actual) + (expected.is_a?(Time) && actual.is_a?(Time)) || + ( + # Check for ActiveSupport's #acts_like_time? for their time-like objects + # (like ActiveSupport::TimeWithZone). + expected.respond_to?(:acts_like_time?) && expected.acts_like_time? && + actual.respond_to?(:acts_like_time?) && actual.acts_like_time? + ) + end + + protected + + def attribute_names + base = ["year", "month", "day", "hour", "min", "sec", "nsec", "zone", "gmt_offset"] + + # If timezones are different, also show a normalized timestamp at the + # end of the diff to help visualize why they are different moments in + # time. + if actual.zone != expected.zone + base + ["utc"] + else + base + end + end + end + end +end diff --git a/spec/integration/rspec/eq_matcher_spec.rb b/spec/integration/rspec/eq_matcher_spec.rb index b239b2ba..93780029 100644 --- a/spec/integration/rspec/eq_matcher_spec.rb +++ b/spec/integration/rspec/eq_matcher_spec.rb @@ -142,7 +142,7 @@ it "produces the correct failure message when used in the negative" do as_both_colored_and_uncolored do |color_enabled| - snippet = %|expect("Jennifer").to eq("Marty")| + snippet = %|expect("Jennifer").not_to eq("Jennifer")| program = make_plain_test_program( snippet, color_enabled: color_enabled, @@ -150,16 +150,152 @@ expected_output = build_expected_output( color_enabled: color_enabled, - snippet: %|expect("Jennifer").to eq("Marty")|, + snippet: %|expect("Jennifer").not_to eq("Jennifer")|, expectation: proc { line do plain "Expected " beta %|"Jennifer"| + plain " not to eq " + alpha %|"Jennifer"| + plain "." + end + }, + ) + + expect(program). + to produce_output_when_run(expected_output). + in_color(color_enabled) + end + end + end + + context "when comparing two different Time instances" do + it "produces the correct failure message when used in the positive" do + as_both_colored_and_uncolored do |color_enabled| + snippet = <<~RUBY + expected = Time.utc(2011, 12, 13, 14, 15, 16) + actual = Time.utc(2011, 12, 13, 14, 15, 16, 500_000) + expect(expected).to eq(actual) + RUBY + program = make_plain_test_program( + snippet, + color_enabled: color_enabled, + ) + + expected_output = build_expected_output( + color_enabled: color_enabled, + snippet: %|expect(expected).to eq(actual)|, + expectation: proc { + line do + plain "Expected " + beta %|2011-12-13 14:15:16.000 UTC +00:00 (Time)| plain " to eq " - alpha %|"Marty"| + alpha %|2011-12-13 14:15:16.500 UTC +00:00 (Time)| plain "." end }, + diff: proc { + plain_line " #