diff --git a/.github/workflows/super_diff.yml b/.github/workflows/super_diff.yml index 672683ec..032b9eba 100644 --- a/.github/workflows/super_diff.yml +++ b/.github/workflows/super_diff.yml @@ -65,6 +65,7 @@ jobs: rspec_appraisal: - rspec_lt_3_10 - rspec_gte_3_10 + - rspec_gte_3_13 env: BUNDLE_GEMFILE: gemfiles/${{ matrix.rails_appraisal }}_${{ matrix.rspec_appraisal }}.gemfile steps: diff --git a/Appraisals b/Appraisals index cdc1f860..e7a828f2 100644 --- a/Appraisals +++ b/Appraisals @@ -49,12 +49,25 @@ appraisals = { gem 'rspec-mocks', '3.12.0' gem 'rspec-support', '3.12.0' + gem 'rspec-rails' if with_rails + end, + rspec_gte_3_13: + proc do |with_rails| + # version = ['>= 3.13', '< 4'] + # gem "rspec", *version + + gem 'rspec', '3.13.0' + gem 'rspec-core', '3.13.0' + gem 'rspec-expectations', '3.13.0' + gem 'rspec-mocks', '3.13.0' + gem 'rspec-support', '3.13.0' + gem 'rspec-rails' if with_rails end } rails_appraisals = %i[no_rails rails_6_0 rails_6_1 rails_7_0] -rspec_appraisals = %i[rspec_lt_3_10 rspec_gte_3_10] +rspec_appraisals = %i[rspec_lt_3_10 rspec_gte_3_10 rspec_gte_3_13] rails_appraisals.each do |rails_appraisal| rspec_appraisals.each do |rspec_appraisal| diff --git a/CHANGELOG.md b/CHANGELOG.md index d011f734..91ca0d82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Features + +- Implement RSpec 3.13.0+ compatibility. [#258](https://github.com/splitwise/super_diff/pull/258) + ## 0.14.0 - 2024-11-15 ### Features diff --git a/gemfiles/no_rails_rspec_gte_3_13.gemfile b/gemfiles/no_rails_rspec_gte_3_13.gemfile new file mode 100644 index 00000000..f900f2a1 --- /dev/null +++ b/gemfiles/no_rails_rspec_gte_3_13.gemfile @@ -0,0 +1,23 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "appraisal", git: "https://github.com/thoughtbot/appraisal" +gem "bundler-audit" +gem "childprocess" +gem "climate_control" +gem "prettier_print" +gem "pry-byebug", platform: :mri +gem "pry-nav", platform: :jruby +gem "rake" +gem "rubocop" +gem "syntax_tree" +gem "syntax_tree-haml" +gem "syntax_tree-rbs" +gem "rspec", "3.13.0" +gem "rspec-core", "3.13.0" +gem "rspec-expectations", "3.13.0" +gem "rspec-mocks", "3.13.0" +gem "rspec-support", "3.13.0" + +gemspec path: "../" diff --git a/gemfiles/rails_6_0_rspec_gte_3_13.gemfile b/gemfiles/rails_6_0_rspec_gte_3_13.gemfile new file mode 100644 index 00000000..c427d0f5 --- /dev/null +++ b/gemfiles/rails_6_0_rspec_gte_3_13.gemfile @@ -0,0 +1,30 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "appraisal", git: "https://github.com/thoughtbot/appraisal" +gem "bundler-audit" +gem "childprocess" +gem "climate_control" +gem "prettier_print" +gem "pry-byebug", platform: :mri +gem "pry-nav", platform: :jruby +gem "rake" +gem "rubocop" +gem "syntax_tree" +gem "syntax_tree-haml" +gem "syntax_tree-rbs" +gem "activerecord-jdbcsqlite3-adapter", platform: :jruby +gem "jdbc-sqlite3", platform: :jruby +gem "net-ftp" +gem "combustion" +gem "rails", "~> 6.0.0" +gem "sqlite3", "~> 1.4.0", platform: [:ruby, :mswin, :mingw] +gem "rspec", "3.13.0" +gem "rspec-core", "3.13.0" +gem "rspec-expectations", "3.13.0" +gem "rspec-mocks", "3.13.0" +gem "rspec-support", "3.13.0" +gem "rspec-rails" + +gemspec path: "../" diff --git a/gemfiles/rails_6_1_rspec_gte_3_13.gemfile b/gemfiles/rails_6_1_rspec_gte_3_13.gemfile new file mode 100644 index 00000000..e1059be7 --- /dev/null +++ b/gemfiles/rails_6_1_rspec_gte_3_13.gemfile @@ -0,0 +1,30 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "appraisal", git: "https://github.com/thoughtbot/appraisal" +gem "bundler-audit" +gem "childprocess" +gem "climate_control" +gem "prettier_print" +gem "pry-byebug", platform: :mri +gem "pry-nav", platform: :jruby +gem "rake" +gem "rubocop" +gem "syntax_tree" +gem "syntax_tree-haml" +gem "syntax_tree-rbs" +gem "activerecord-jdbcsqlite3-adapter", platform: :jruby +gem "jdbc-sqlite3", platform: :jruby +gem "net-ftp" +gem "combustion" +gem "rails", "~> 6.1.0" +gem "sqlite3", "~> 1.4.0", platform: [:ruby, :mswin, :mingw] +gem "rspec", "3.13.0" +gem "rspec-core", "3.13.0" +gem "rspec-expectations", "3.13.0" +gem "rspec-mocks", "3.13.0" +gem "rspec-support", "3.13.0" +gem "rspec-rails" + +gemspec path: "../" diff --git a/gemfiles/rails_7_0_rspec_gte_3_13.gemfile b/gemfiles/rails_7_0_rspec_gte_3_13.gemfile new file mode 100644 index 00000000..51c4fd39 --- /dev/null +++ b/gemfiles/rails_7_0_rspec_gte_3_13.gemfile @@ -0,0 +1,30 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "appraisal", git: "https://github.com/thoughtbot/appraisal" +gem "bundler-audit" +gem "childprocess" +gem "climate_control" +gem "prettier_print" +gem "pry-byebug", platform: :mri +gem "pry-nav", platform: :jruby +gem "rake" +gem "rubocop" +gem "syntax_tree" +gem "syntax_tree-haml" +gem "syntax_tree-rbs" +gem "activerecord-jdbcsqlite3-adapter", platform: :jruby +gem "jdbc-sqlite3", platform: :jruby +gem "net-ftp" +gem "combustion" +gem "rails", "~> 7.0.0" +gem "sqlite3", "~> 1.4.0", platform: [:ruby, :mswin, :mingw] +gem "rspec", "3.13.0" +gem "rspec-core", "3.13.0" +gem "rspec-expectations", "3.13.0" +gem "rspec-mocks", "3.13.0" +gem "rspec-support", "3.13.0" +gem "rspec-rails" + +gemspec path: "../" diff --git a/lib/super_diff/rspec.rb b/lib/super_diff/rspec.rb index dfb6bad6..9a89a91f 100644 --- a/lib/super_diff/rspec.rb +++ b/lib/super_diff/rspec.rb @@ -81,7 +81,16 @@ def self.a_value_within_something?(value) end def self.aliased_matcher?(value) - value.is_a?(::RSpec::Matchers::AliasedMatcher) + if SuperDiff::RSpec.rspec_version < '3.13.0' + value.is_a?(::RSpec::Matchers::AliasedMatcher) + else # See Github issue #250. + !ordered_options?(value) && value.respond_to?(:base_matcher) + end + end + + def self.ordered_options?(value) + defined?(::ActiveSupport::OrderedOptions) && + value.is_a?(::ActiveSupport::OrderedOptions) end def self.rspec_version diff --git a/lib/super_diff/rspec/monkey_patches.rb b/lib/super_diff/rspec/monkey_patches.rb index b4bd6cfe..6e063ffe 100644 --- a/lib/super_diff/rspec/monkey_patches.rb +++ b/lib/super_diff/rspec/monkey_patches.rb @@ -309,73 +309,146 @@ def format(value) end module Matchers - class ExpectedsForMultipleDiffs - SuperDiff.insert_singleton_overrides(self) do - # Add a key for different sides - def from(expected) - return expected if self === expected - - text = colorizer.wrap("Diff:", SuperDiff.configuration.header_color) - - if SuperDiff.configuration.key_enabled? - text += - "\n\n" + - colorizer.wrap( - "┌ (Key) ──────────────────────────┐", - SuperDiff.configuration.border_color - ) + "\n" + - colorizer.wrap("│ ", SuperDiff.configuration.border_color) + - colorizer.wrap( - "‹-› in expected, not in actual", - SuperDiff.configuration.expected_color - ) + - colorizer.wrap(" │", SuperDiff.configuration.border_color) + - "\n" + - colorizer.wrap("│ ", SuperDiff.configuration.border_color) + - colorizer.wrap( - "‹+› in actual, not in expected", - SuperDiff.configuration.actual_color - ) + - colorizer.wrap(" │", SuperDiff.configuration.border_color) + - "\n" + - colorizer.wrap("│ ", SuperDiff.configuration.border_color) + - "‹ › in both expected and actual" + - colorizer.wrap(" │", SuperDiff.configuration.border_color) + - "\n" + - colorizer.wrap( - "└─────────────────────────────────┘", - SuperDiff.configuration.border_color - ) + if SuperDiff::RSpec.rspec_version < "3.13.0" + class ExpectedsForMultipleDiffs + SuperDiff.insert_singleton_overrides(self) do + # Add a key for different sides + def from(expected) + return expected if self === expected + + text = colorizer.wrap("Diff:", SuperDiff.configuration.header_color) + + if SuperDiff.configuration.key_enabled? + text += + "\n\n" + + colorizer.wrap( + "┌ (Key) ──────────────────────────┐", + SuperDiff.configuration.border_color + ) + "\n" + + colorizer.wrap("│ ", SuperDiff.configuration.border_color) + + colorizer.wrap( + "‹-› in expected, not in actual", + SuperDiff.configuration.expected_color + ) + + colorizer.wrap(" │", SuperDiff.configuration.border_color) + + "\n" + + colorizer.wrap("│ ", SuperDiff.configuration.border_color) + + colorizer.wrap( + "‹+› in actual, not in expected", + SuperDiff.configuration.actual_color + ) + + colorizer.wrap(" │", SuperDiff.configuration.border_color) + + "\n" + + colorizer.wrap("│ ", SuperDiff.configuration.border_color) + + "‹ › in both expected and actual" + + colorizer.wrap(" │", SuperDiff.configuration.border_color) + + "\n" + + colorizer.wrap( + "└─────────────────────────────────┘", + SuperDiff.configuration.border_color + ) + end + + new([[expected, text]]) end - new([[expected, text]]) + def colorizer + RSpec::Core::Formatters::ConsoleCodes + end end - def colorizer - RSpec::Core::Formatters::ConsoleCodes + SuperDiff.insert_overrides(self) do + # Add an extra line break + def message_with_diff(message, differ, actual) + diff = diffs(differ, actual) + + diff.empty? ? message : "#{message.rstrip}\n\n#{diff}" + end + + private + + # Add extra line breaks in between diffs, and colorize the word "Diff" + def diffs(differ, actual) + @expected_list + .map do |(expected, diff_label)| + diff = differ.diff(actual, expected) + next if diff.strip.empty? + diff_label + diff + end + .compact + .join("\n\n") + end end end + else + class MultiMatcherDiff + SuperDiff.insert_singleton_overrides(self) do + # Add a key for different sides + def from(expected, actual) + return expected if self === expected + + text = colorizer.wrap("Diff:", SuperDiff.configuration.header_color) + + if SuperDiff.configuration.key_enabled? + text += + "\n\n" + + colorizer.wrap( + "┌ (Key) ──────────────────────────┐", + SuperDiff.configuration.border_color + ) + "\n" + + colorizer.wrap("│ ", SuperDiff.configuration.border_color) + + colorizer.wrap( + "‹-› in expected, not in actual", + SuperDiff.configuration.expected_color + ) + + colorizer.wrap(" │", SuperDiff.configuration.border_color) + + "\n" + + colorizer.wrap("│ ", SuperDiff.configuration.border_color) + + colorizer.wrap( + "‹+› in actual, not in expected", + SuperDiff.configuration.actual_color + ) + + colorizer.wrap(" │", SuperDiff.configuration.border_color) + + "\n" + + colorizer.wrap("│ ", SuperDiff.configuration.border_color) + + "‹ › in both expected and actual" + + colorizer.wrap(" │", SuperDiff.configuration.border_color) + + "\n" + + colorizer.wrap( + "└─────────────────────────────────┘", + SuperDiff.configuration.border_color + ) + end - SuperDiff.insert_overrides(self) do - # Add an extra line break - def message_with_diff(message, differ, actual) - diff = diffs(differ, actual) + new([[expected, text, actual]]) + end - diff.empty? ? message : "#{message.rstrip}\n\n#{diff}" + def colorizer + RSpec::Core::Formatters::ConsoleCodes + end end - private + SuperDiff.insert_overrides(self) do + # Add an extra line break + def message_with_diff(message, differ) + diff = diffs(differ) + + diff.empty? ? message : "#{message.rstrip}\n\n#{diff}" + end - # Add extra line breaks in between diffs, and colorize the word "Diff" - def diffs(differ, actual) - @expected_list - .map do |(expected, diff_label)| - diff = differ.diff(actual, expected) - next if diff.strip.empty? - diff_label + diff - end - .compact - .join("\n\n") + private + + # Add extra line breaks in between diffs, and colorize the word "Diff" + def diffs(differ) + @expected_list + .map do |(expected, diff_label, actual)| + diff = differ.diff(actual, expected) + next if diff.strip.empty? + diff_label + diff + end + .compact + .join("\n\n") + end end end end