Skip to content

Commit 2ad6284

Browse files
Mangemcmire
authored andcommitted
Add useful diff representation of Time-like values
This should add a more useful diff for Time instances, as well as make the diff output from ActiveSupport::TimeWithZone instances readable. Previously the ActiveSupport::TimeWithZone diff output would contain all the timezones, leading to several thousand lines in the diff. Now all time-like instances will work the same way: Single-line diffs will be of a simple ISO-like string, while the full diff will show which parts of the time actually differs. Timezone is promptly shown, both with name and with GMT offset.
1 parent 65caef7 commit 2ad6284

File tree

8 files changed

+201
-3
lines changed

8 files changed

+201
-3
lines changed

lib/super_diff/differs.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ module Differs
77
autoload :Empty, "super_diff/differs/empty"
88
autoload :Hash, "super_diff/differs/hash"
99
autoload :MultilineString, "super_diff/differs/multiline_string"
10+
autoload :Time, "super_diff/differs/time"
1011

1112
DEFAULTS = [
1213
Array,
1314
Hash,
15+
Time,
1416
MultilineString,
1517
CustomObject,
1618
DefaultObject,

lib/super_diff/differs/time.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module SuperDiff
2+
module Differs
3+
class Time < Base
4+
def self.applies_to?(expected, actual)
5+
OperationalSequencers::TimeLike.applies_to?(expected, actual)
6+
end
7+
8+
def call
9+
operations.to_diff(indent_level: indent_level)
10+
end
11+
12+
private
13+
14+
def operations
15+
OperationalSequencers::TimeLike.call(
16+
expected: expected,
17+
actual: actual,
18+
extra_operational_sequencer_classes: extra_operational_sequencer_classes,
19+
extra_diff_formatter_classes: extra_diff_formatter_classes,
20+
)
21+
end
22+
end
23+
end
24+
end

lib/super_diff/object_inspection/inspectors.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module Inspectors
1313
autoload :Hash, "super_diff/object_inspection/inspectors/hash"
1414
autoload :Primitive, "super_diff/object_inspection/inspectors/primitive"
1515
autoload :String, "super_diff/object_inspection/inspectors/string"
16+
autoload :Time, "super_diff/object_inspection/inspectors/time"
1617
end
1718
end
1819
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module SuperDiff
2+
module ObjectInspection
3+
module Inspectors
4+
TIME_FORMAT = "%Y-%m-%d %H:%M:%S.%3N %Z %:z".freeze
5+
6+
Time = InspectionTree.new do
7+
add_text do |time|
8+
"#{time.strftime(TIME_FORMAT)} (#{time.class})"
9+
end
10+
end
11+
end
12+
end
13+
end

lib/super_diff/object_inspection/map.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ def call(object)
1616
Inspectors::Hash
1717
when String
1818
Inspectors::String
19+
when Time
20+
Inspectors::Time
1921
when true, false, nil, Symbol, Numeric, Regexp, Class
2022
Inspectors::Primitive
2123
else

lib/super_diff/operational_sequencers.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module OperationalSequencers
1010
:MultilineString,
1111
"super_diff/operational_sequencers/multiline_string",
1212
)
13+
autoload :TimeLike, "super_diff/operational_sequencers/time_like"
1314

1415
DEFAULTS = [Array, Hash, CustomObject].freeze
1516
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module SuperDiff
2+
module OperationalSequencers
3+
class TimeLike < CustomObject
4+
def self.applies_to?(expected, actual)
5+
(expected.is_a?(Time) && actual.is_a?(Time)) ||
6+
(
7+
# Check for ActiveSupport's #acts_like_time? for their time-like objects
8+
# (like ActiveSupport::TimeWithZone).
9+
expected.respond_to?(:acts_like_time?) && expected.acts_like_time? &&
10+
actual.respond_to?(:acts_like_time?) && actual.acts_like_time?
11+
)
12+
end
13+
14+
protected
15+
16+
def attribute_names
17+
["year", "month", "day", "hour", "min", "sec", "nsec", "zone", "gmt_offset"]
18+
end
19+
end
20+
end
21+
end

spec/integration/rspec/eq_matcher_spec.rb

Lines changed: 137 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,24 +142,158 @@
142142

143143
it "produces the correct failure message when used in the negative" do
144144
as_both_colored_and_uncolored do |color_enabled|
145-
snippet = %|expect("Jennifer").to eq("Marty")|
145+
snippet = %|expect("Jennifer").not_to eq("Jennifer")|
146146
program = make_plain_test_program(
147147
snippet,
148148
color_enabled: color_enabled,
149149
)
150150

151151
expected_output = build_expected_output(
152152
color_enabled: color_enabled,
153-
snippet: %|expect("Jennifer").to eq("Marty")|,
153+
snippet: %|expect("Jennifer").not_to eq("Jennifer")|,
154154
expectation: proc {
155155
line do
156156
plain "Expected "
157157
beta %|"Jennifer"|
158+
plain " not to eq "
159+
alpha %|"Jennifer"|
160+
plain "."
161+
end
162+
},
163+
)
164+
165+
expect(program).
166+
to produce_output_when_run(expected_output).
167+
in_color(color_enabled)
168+
end
169+
end
170+
end
171+
172+
context "when comparing two different Time instances" do
173+
it "produces the correct failure message when used in the positive" do
174+
as_both_colored_and_uncolored do |color_enabled|
175+
snippet = <<~RUBY
176+
expected = Time.utc(2011, 12, 13, 14, 15, 16)
177+
actual = Time.utc(2011, 12, 13, 14, 15, 16, 500_000)
178+
expect(expected).to eq(actual)
179+
RUBY
180+
program = make_plain_test_program(
181+
snippet,
182+
color_enabled: color_enabled,
183+
)
184+
185+
expected_output = build_expected_output(
186+
color_enabled: color_enabled,
187+
snippet: %|expect(expected).to eq(actual)|,
188+
expectation: proc {
189+
line do
190+
plain "Expected "
191+
beta %|2011-12-13 14:15:16.000 UTC +00:00 (Time)|
158192
plain " to eq "
159-
alpha %|"Marty"|
193+
alpha %|2011-12-13 14:15:16.500 UTC +00:00 (Time)|
160194
plain "."
161195
end
162196
},
197+
diff: proc {
198+
plain_line " #<Time {"
199+
plain_line " year: 2011,"
200+
plain_line " month: 12,"
201+
plain_line " day: 13,"
202+
plain_line " hour: 14,"
203+
plain_line " min: 15,"
204+
plain_line " sec: 16,"
205+
alpha_line "- nsec: 500000000,"
206+
beta_line "+ nsec: 0,"
207+
plain_line " zone: \"UTC\","
208+
plain_line " gmt_offset: 0"
209+
plain_line " }>"
210+
},
211+
)
212+
213+
expect(program).
214+
to produce_output_when_run(expected_output).
215+
in_color(color_enabled)
216+
end
217+
end
218+
219+
it "produces the correct failure message when used in the negative" do
220+
as_both_colored_and_uncolored do |color_enabled|
221+
snippet = <<~RUBY
222+
time = Time.utc(2011, 12, 13, 14, 15, 16)
223+
expect(time).not_to eq(time)
224+
RUBY
225+
program = make_plain_test_program(
226+
snippet,
227+
color_enabled: color_enabled,
228+
)
229+
230+
expected_output = build_expected_output(
231+
color_enabled: color_enabled,
232+
snippet: %|expect(time).not_to eq(time)|,
233+
newline_before_expectation: true,
234+
expectation: proc {
235+
line do
236+
plain " Expected "
237+
beta %|2011-12-13 14:15:16.000 UTC +00:00 (Time)|
238+
end
239+
240+
line do
241+
plain "not to eq "
242+
alpha %|2011-12-13 14:15:16.000 UTC +00:00 (Time)|
243+
end
244+
},
245+
)
246+
247+
expect(program).
248+
to produce_output_when_run(expected_output).
249+
in_color(color_enabled)
250+
end
251+
end
252+
end
253+
254+
context "when comparing two different Time and ActiveSupport::TimeWithZone instances" do
255+
it "produces the correct failure message when used in the positive" do
256+
as_both_colored_and_uncolored do |color_enabled|
257+
snippet = <<~RUBY
258+
expected = Time.utc(2011, 12, 13, 14, 15, 16)
259+
actual = Time.utc(2011, 12, 13, 15, 15, 16).in_time_zone("Europe/Stockholm")
260+
expect(expected).to eq(actual)
261+
RUBY
262+
program = make_rspec_rails_test_program(
263+
snippet,
264+
color_enabled: color_enabled,
265+
)
266+
267+
expected_output = build_expected_output(
268+
color_enabled: color_enabled,
269+
snippet: %|expect(expected).to eq(actual)|,
270+
expectation: proc {
271+
line do
272+
plain "Expected "
273+
beta %|2011-12-13 14:15:16.000 UTC +00:00 (Time)|
274+
end
275+
276+
line do
277+
plain " to eq "
278+
alpha %|2011-12-13 16:15:16.000 CET +01:00 (ActiveSupport::TimeWithZone)|
279+
end
280+
},
281+
diff: proc {
282+
plain_line " #<ActiveSupport::TimeWithZone {"
283+
plain_line " year: 2011,"
284+
plain_line " month: 12,"
285+
plain_line " day: 13,"
286+
alpha_line "- hour: 16,"
287+
beta_line "+ hour: 14,"
288+
plain_line " min: 15,"
289+
plain_line " sec: 16,"
290+
plain_line " nsec: 0,"
291+
alpha_line "- zone: \"CET\","
292+
beta_line "+ zone: \"UTC\","
293+
alpha_line "- gmt_offset: 3600"
294+
beta_line "+ gmt_offset: 0"
295+
plain_line " }>"
296+
},
163297
)
164298

165299
expect(program).

0 commit comments

Comments
 (0)