diff --git a/CHANGELOG.md b/CHANGELOG.md index f4a5de6a..2088fe1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Support elision for flat line trees. [#300](https://github.com/splitwise/super_diff/pull/300) by [@gschlager](https://github.com/gschlager) - Fix integration snippet indentation. [#299](https://github.com/splitwise/super_diff/pull/299) by [@gschlager](https://github.com/gschlager) - Pin all actions to full commit SHA. [#305](https://github.com/splitwise/super_diff/pull/305) +- Do not attempt to sub-diff multiline strings. [#304](https://github.com/splitwise/super_diff/pull/304) ## 0.18.0 - 2025-12-05 diff --git a/lib/super_diff/basic/operation_tree_builders/multiline_string.rb b/lib/super_diff/basic/operation_tree_builders/multiline_string.rb index 714d13d6..43fc44ff 100644 --- a/lib/super_diff/basic/operation_tree_builders/multiline_string.rb +++ b/lib/super_diff/basic/operation_tree_builders/multiline_string.rb @@ -43,6 +43,13 @@ def build_operation_tree attr_reader :sequence_matcher, :original_expected, :original_actual + # override + def should_compare?(_operation, _next_operation) + # Don't try to build a nested operation tree for individual line changes, even if + # there's an applicable string operation tree builder. + false + end + def split_into_lines(string) string.scan(/.*(?:\r|\n|\r\n|\Z)/) end diff --git a/spec/unit/basic/operation_tree_builders/multiline_string_spec.rb b/spec/unit/basic/operation_tree_builders/multiline_string_spec.rb index adc274cf..bfc73f48 100644 --- a/spec/unit/basic/operation_tree_builders/multiline_string_spec.rb +++ b/spec/unit/basic/operation_tree_builders/multiline_string_spec.rb @@ -2,6 +2,41 @@ require 'spec_helper' +class WordOperationTreeBuilder < SuperDiff::Core::AbstractOperationTreeBuilder + def self.applies_to?(expected, actual) + expected.is_a?(::String) && actual.is_a?(::String) + end + + protected + + def unary_operations + expected_words = expected.split + actual_words = actual.split + + Diff::LCS.diff(expected_words, actual_words).flat_map do |change_group| + change_group.map do |change| + SuperDiff::Core::UnaryOperation.new( + name: change.action == '+' ? :insert : :delete, + collection: expected, + key: change.position, + index: change.position, + value: change.element + ) + end + end + end + + def build_operation_tree + SuperDiff::Basic::OperationTrees::Array.new([]) + end + + private + + def should_compare?(_operation, _next_operation) + false + end +end + RSpec.describe SuperDiff, type: :unit do describe '.diff' do subject(:diff) { SuperDiff.diff(expected, actual) } @@ -115,5 +150,33 @@ expect(diff).to eq(expected_output) end end + + context 'when a string operation tree builder exists' do + around do |example| + with_configuration(extra_operation_tree_builder_classes: SuperDiff.configuration.extra_operation_tree_builder_classes + [WordOperationTreeBuilder]) do + example.run + end + end + + let(:actual) { <<~STRING } + This here is a string. + It contains separate lines. + This one is different. + STRING + + it 'does not attempt to diff each line' do + expected_output = + SuperDiff::Core::Helpers + .style(color_enabled: true) do + plain_line ' This here is a string.\\n' + plain_line ' It contains separate lines.\\n' + expected_line '- What else can I say?\\n' + actual_line '+ This one is different.\\n' + end + .to_s + .chomp + expect(diff).to eq(expected_output) + end + end end end