Skip to content
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [Unreleased]

### Features

- Add better support for Data object diffing. [#259](https://github.com/splitwise/super_diff/pull/224)

## 0.12.1 - 2024-04-26

Note that since 0.12.0 has been yanked, changes for this version are listed
Expand Down
4 changes: 3 additions & 1 deletion lib/super_diff/basic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module Basic
InspectionTreeBuilders::Primitive,
InspectionTreeBuilders::TimeLike,
InspectionTreeBuilders::DateLike,
InspectionTreeBuilders::DataObject,
InspectionTreeBuilders::DefaultObject
)

Expand All @@ -34,7 +35,8 @@ module Basic
OperationTreeBuilders::Hash,
OperationTreeBuilders::TimeLike,
OperationTreeBuilders::DateLike,
OperationTreeBuilders::CustomObject
OperationTreeBuilders::CustomObject,
OperationTreeBuilders::DataObject
)

config.add_extra_operation_tree_classes(
Expand Down
4 changes: 4 additions & 0 deletions lib/super_diff/basic/inspection_tree_builders.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ module InspectionTreeBuilders
:CustomObject,
"super_diff/basic/inspection_tree_builders/custom_object"
)
autoload(
:DataObject,
"super_diff/basic/inspection_tree_builders/data_object"
)
autoload(
:DefaultObject,
"super_diff/basic/inspection_tree_builders/default_object"
Expand Down
40 changes: 40 additions & 0 deletions lib/super_diff/basic/inspection_tree_builders/data_object.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module SuperDiff
module Basic
module InspectionTreeBuilders
class DataObject < Core::AbstractInspectionTreeBuilder
def self.applies_to?(value)
SuperDiff::Core::Helpers.ruby_version_matches?("~> 3.2") &&
value.is_a?(Data)
end

def call
Core::InspectionTree.new do |t1|
t1.as_lines_when_rendering_to_lines(
collection_bookend: :open
) do |t2|
t2.add_text "#<data #{object.class.name} "

# stree-ignore
t2.when_rendering_to_lines do |t3|
t3.add_text "{"
end
end

t1.nested { |t2| t2.insert_hash_inspection_of(object.to_h) }

t1.as_lines_when_rendering_to_lines(
collection_bookend: :close
) do |t2|
# stree-ignore
t2.when_rendering_to_lines do |t3|
t3.add_text "}"
end

t2.add_text ">"
end
end
end
end
end
end
end
4 changes: 4 additions & 0 deletions lib/super_diff/basic/operation_tree_builders.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ module OperationTreeBuilders
:CustomObject,
"super_diff/basic/operation_tree_builders/custom_object"
)
autoload(
:DataObject,
"super_diff/basic/operation_tree_builders/data_object"
)
autoload(
:DefaultObject,
"super_diff/basic/operation_tree_builders/default_object"
Expand Down
18 changes: 18 additions & 0 deletions lib/super_diff/basic/operation_tree_builders/data_object.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module SuperDiff
module Basic
module OperationTreeBuilders
class DataObject < CustomObject
def self.applies_to?(expected, actual)
SuperDiff::Core::Helpers.ruby_version_matches?("~> 3.2") &&
expected.class == actual.class && expected.is_a?(Data)
end

protected

def attribute_names
expected.members & actual.members
end
end
end
end
end
7 changes: 7 additions & 0 deletions spec/support/models/point.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
if defined?(Data)
module SuperDiff
module Test
Point = Data.define(:x, :y)
end
end
end
146 changes: 146 additions & 0 deletions spec/unit/basic/inspection_tree_builders/data_object_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
require "spec_helper"

if defined?(Data)
RSpec.describe SuperDiff, type: :unit do
describe ".inspect_object" do
context "given as_lines: false" do
subject(:output) do
described_class.inspect_object(object, as_lines: false)
end

context "given an anonymous Data object" do
let(:object) { Data.define(:x, :y).new(1, 2) }

it "shows the data" do
expect(output).to eq("#<data x: 1, y: 2>")
end
end

context "given a named Data object" do
let(:object) { SuperDiff::Test::Point.new(1, 2) }

it "shows the data" do
expect(output).to eq("#<data SuperDiff::Test::Point x: 1, y: 2>")
end
end

context "given a Data object that defines #attributes_for_super_diff" do
let(:klass) do
Data.define(:x, :y) do
def attributes_for_super_diff
{ beep: :boop }
end
end
end
let(:object) { klass.new(1, 2) }

it "uses the custom attributes" do
expect(output).to start_with("#<#<Class:0x").and end_with(
"beep: :boop>"
)
end
end
end

context "given as_lines: true" do
subject(:tiered_lines) do
described_class.inspect_object(
object,
as_lines: true,
type: :noop,
indentation_level: 1
)
end

context "given an anonymous Data object" do
let(:object) { Data.define(:x, :y).new(1, 2) }

it "shows the data" do
expect(tiered_lines).to match(
[
an_object_having_attributes(
value: "#<data {",
collection_bookend: :open
),
an_object_having_attributes(
prefix: "x: ",
value: "1",
add_comma: true
),
an_object_having_attributes(
prefix: "y: ",
value: "2",
add_comma: false
),
an_object_having_attributes(
value: "}>",
collection_bookend: :close
)
]
)
end
end

context "given a named Data object" do
let(:object) { SuperDiff::Test::Point.new(1, 2) }

it "shows the data" do
expect(tiered_lines).to match(
[
an_object_having_attributes(
value: "#<data SuperDiff::Test::Point {",
collection_bookend: :open
),
an_object_having_attributes(
prefix: "x: ",
value: "1",
add_comma: true
),
an_object_having_attributes(
prefix: "y: ",
value: "2",
add_comma: false
),
an_object_having_attributes(
value: "}>",
collection_bookend: :close
)
]
)
end
end

context "given a Data object that defines #attributes_for_super_diff" do
let(:klass) do
Data.define(:x, :y) do
def attributes_for_super_diff
{ beep: :boop }
end
end
end
let(:object) { klass.new(1, 2) }

it "uses the custom attributes" do
expect(tiered_lines).to match(
[
an_object_having_attributes(
value: /\A#<#<Class:0x.*> {/,
collection_bookend: :open
),
an_object_having_attributes(
prefix: "beep: ",
value: ":boop",
add_comma: false
),
an_object_having_attributes(
value: "}>",
collection_bookend: :close
)
]
)
end
end
end
end
end
end
82 changes: 82 additions & 0 deletions spec/unit/basic/operation_tree_builders/data_object_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
require "spec_helper"

if defined?(Data)
RSpec.describe SuperDiff, type: :unit do
describe ".diff" do
subject(:diff) { described_class.diff(a, b) }

context "when given two Data objects of the same class" do
let(:a) { SuperDiff::Test::Point.new(1, 2) }
let(:b) { SuperDiff::Test::Point.new(1, 3) }

it "diffs their member attributes" do
expected_output =
SuperDiff::Core::Helpers
.style(color_enabled: true) do
plain_line " #<SuperDiff::Test::Point {"
plain_line " x: 1,"
expected_line "- y: 2"
actual_line "+ y: 3"
plain_line " }>"
end
.to_s
.chomp

expect(diff).to eq(expected_output)
end

context "when the Data class defines #attributes_for_super_diff" do
let(:klass) do
Class.new(Data.define(:attribute)) do
def self.to_s = "TestClass"

def attributes_for_super_diff
{ attribute: :does_not_matter }
end
end
end

let(:a) { klass.new(1) }
let(:b) { klass.new(2) }

it "diffs their member attributes" do
expected_output =
SuperDiff::Core::Helpers
.style(color_enabled: true) do
plain_line " #<TestClass {"
expected_line "- attribute: 1"
actual_line "+ attribute: 2"
plain_line " }>"
end
.to_s
.chomp

expect(diff).to eq(expected_output)
end
end
end

context "when given two Data objects of different classes" do
let(:a) { SuperDiff::Test::Point.new(1, 2) }
let(:b) { Data.define(:one, :two).new(1, 2) }

it "raises" do
expect { SuperDiff.diff(a, b) }.to raise_error(
SuperDiff::Core::NoDifferAvailableError
)
end
end

context "when given a Data object and a hash" do
let(:a) { Data.define(:one, :two).new(1, 2) }
let(:b) { { one: 1, two: 2 } }

it "raises" do
expect { SuperDiff.diff(a, b) }.to raise_error(
SuperDiff::Core::NoDifferAvailableError
)
end
end
end
end
end