diff --git a/lib/super_diff/action_dispatch.rb b/lib/super_diff/action_dispatch.rb new file mode 100644 index 00000000..19fb0e02 --- /dev/null +++ b/lib/super_diff/action_dispatch.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'super_diff/action_dispatch/inspection_tree_builders' + +module SuperDiff + module ActionDispatch + SuperDiff.configure do |config| + config.prepend_extra_inspection_tree_builder_classes( + InspectionTreeBuilders::Request + ) + end + end +end diff --git a/lib/super_diff/action_dispatch/inspection_tree_builders.rb b/lib/super_diff/action_dispatch/inspection_tree_builders.rb new file mode 100644 index 00000000..66d0c45f --- /dev/null +++ b/lib/super_diff/action_dispatch/inspection_tree_builders.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module SuperDiff + module ActionDispatch + module InspectionTreeBuilders + autoload( + :Request, + 'super_diff/action_dispatch/inspection_tree_builders/request' + ) + end + end +end diff --git a/lib/super_diff/action_dispatch/inspection_tree_builders/request.rb b/lib/super_diff/action_dispatch/inspection_tree_builders/request.rb new file mode 100644 index 00000000..0c30c9c2 --- /dev/null +++ b/lib/super_diff/action_dispatch/inspection_tree_builders/request.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module SuperDiff + module ActionDispatch + module InspectionTreeBuilders + class Request < Core::AbstractInspectionTreeBuilder + def self.applies_to?(value) + value.is_a?(::ActionDispatch::Request) + end + + def call + Core::InspectionTree.new do |t1| + t1.as_lines_when_rendering_to_lines do |t2| + t2.add_text object.inspect + end + end + end + end + end + end +end diff --git a/lib/super_diff/rails.rb b/lib/super_diff/rails.rb index 52aa99ae..a953eb26 100644 --- a/lib/super_diff/rails.rb +++ b/lib/super_diff/rails.rb @@ -2,3 +2,4 @@ require 'super_diff/active_support' require 'super_diff/active_record' if defined?(ActiveRecord) +require 'super_diff/action_dispatch' if defined?(ActionDispatch) diff --git a/spec/integration/rails/action_dispatch_spec.rb b/spec/integration/rails/action_dispatch_spec.rb new file mode 100644 index 00000000..b0fa86b8 --- /dev/null +++ b/spec/integration/rails/action_dispatch_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Integration with ActionDispatch', type: :integration, action_dispatch: true do + context "when using 'super_diff/rspec-rails'" do + include_context 'integration with ActionDispatch' + + def make_program(test, color_enabled:) + make_rspec_rails_test_program(test, color_enabled: color_enabled) + end + end + + context "when using 'super_diff/action_dispatch'" do + include_context 'integration with ActionDispatch' + + def make_program(test, color_enabled:) + make_rspec_action_dispatch_program(test, color_enabled: color_enabled) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4492ad3d..33167965 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -25,6 +25,12 @@ active_record_available = false end +begin + require 'action_dispatch' +rescue LoadError + # ActionDispatch isn't available (presumably because we're not testing it) +end + Dir .glob(File.expand_path('support/**/*.rb', __dir__)) .reject { |file| file.include?('/models/active_record/') && !active_record_available } @@ -52,6 +58,7 @@ config.default_formatter = 'documentation' unless %w[true 1].include?(ENV.fetch('CI', nil)) config.filter_run_excluding active_record: true unless active_record_available + config.filter_run_excluding action_dispatch: true unless defined?(ActionDispatch) config.filter_run_excluding active_support: true unless defined?(ActiveSupport) config.filter_run_excluding with_superdiff_rspec: false diff --git a/spec/support/integration/helpers.rb b/spec/support/integration/helpers.rb index cf9c8bee..c770a836 100644 --- a/spec/support/integration/helpers.rb +++ b/spec/support/integration/helpers.rb @@ -30,6 +30,10 @@ def make_rspec_active_support_program(test, color_enabled:) TestPrograms::RSpecActiveSupport.new(test, color_enabled: color_enabled) end + def make_rspec_action_dispatch_program(test, color_enabled:) + TestPrograms::RSpecActionDispatch.new(test, color_enabled: color_enabled) + end + def make_rspec_rails_test_program(test, color_enabled:) TestPrograms::RSpecRails.new(test, color_enabled: color_enabled) end diff --git a/spec/support/integration/test_programs/rspec_action_dispatch.rb b/spec/support/integration/test_programs/rspec_action_dispatch.rb new file mode 100644 index 00000000..3874d4a6 --- /dev/null +++ b/spec/support/integration/test_programs/rspec_action_dispatch.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module SuperDiff + module IntegrationTests + module TestPrograms + class RSpecActionDispatch < Base + protected + + def test_plan_prelude + <<~PRELUDE.strip + test_plan.boot + test_plan.boot_action_dispatch + PRELUDE + end + + def test_plan_command + 'run_rspec_action_dispatch_test' + end + end + end + end +end diff --git a/spec/support/shared_examples/action_dispatch.rb b/spec/support/shared_examples/action_dispatch.rb new file mode 100644 index 00000000..eb2e8155 --- /dev/null +++ b/spec/support/shared_examples/action_dispatch.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +shared_examples_for 'integration with ActionDispatch' do + context 'when testing attributes of an ActionDispatch::TestResponse' do + it 'produces the correct failure message when used in the positive' do + as_both_colored_and_uncolored do |color_enabled| + snippet = <<~RUBY + request = ActionDispatch::TestRequest.create + response = ActionDispatch::TestResponse.new(200, {}, []).tap do |response| + response.request = request + end + + # The other attributes of TestResponse differ across Rails versions. We don't care about them + # for the purposes of this test. + ActionDispatch::TestResponse.define_method(:attributes_for_super_diff) { {request: request} } + + expect(response).to be_bad_request + RUBY + program = + make_rspec_action_dispatch_program(snippet, color_enabled: color_enabled) + + expected_output = + build_expected_output( + color_enabled: color_enabled, + snippet: 'expect(response).to be_bad_request', + newline_before_expectation: true, + expectation: + proc do + line do + plain ' Expected ' + actual '#>' + end + + line do + plain 'to return a truthy result for ' + expected 'bad_request?' + plain ' or ' + expected 'bad_requests?' + end + end + ) + + expect(program).to produce_output_when_run(expected_output).in_color( + color_enabled + ) + end + end + end +end diff --git a/spec/unit/action_dispatch/object_inspection_spec.rb b/spec/unit/action_dispatch/object_inspection_spec.rb new file mode 100644 index 00000000..a12c34ef --- /dev/null +++ b/spec/unit/action_dispatch/object_inspection_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SuperDiff, type: :unit do + describe '.inspect_object', 'for ActionDispatch objects', action_dispatch: true do + context 'given an ActionDispatch::Request' do + context 'given as_lines: false' do + it 'returns an inspected version of the object' do + string = + described_class.inspect_object( + ActionDispatch::Request.new( + { + 'REQUEST_METHOD' => 'PUT', + 'REMOTE_ADDR' => '10.0.0.1', + Rack::RACK_URL_SCHEME => 'http', + 'HTTP_HOST' => 'host.local' + } + ), + as_lines: false + ) + expect(string).to eq( + %(#) + ) + end + end + + context 'given as_lines: true' do + it 'returns an inspected version of the object on one line' do + tiered_lines = + described_class.inspect_object( + ActionDispatch::Request.new( + { + 'REQUEST_METHOD' => 'PUT', + 'REMOTE_ADDR' => '10.0.0.1', + Rack::RACK_URL_SCHEME => 'http', + 'HTTP_HOST' => 'host.local' + } + ), + as_lines: true, + type: :noop, + indentation_level: 1 + ) + + expect(tiered_lines).to match( + [ + an_object_having_attributes( + type: :noop, + indentation_level: 1, + value: '#' + ) + ] + ) + end + end + end + end +end diff --git a/support/test_plan.rb b/support/test_plan.rb index a090caa8..434e68e2 100644 --- a/support/test_plan.rb +++ b/support/test_plan.rb @@ -62,7 +62,14 @@ def boot_active_support require 'active_support/core_ext/hash/indifferent_access' rescue LoadError => e # active_support may not be in the Gemfile, so that's okay - puts "Error in TestPlan#boot_active_support: #{e.message}" + puts "Error in TestPlan##{__method__}: #{e.message}" + end + + def boot_action_dispatch + require 'action_dispatch' + rescue LoadError => e + # action_dispatch may not be in the Gemfile, so that's okay + puts "Error in TestPlan##{__method__}: #{e.message}" end def boot_active_record @@ -86,7 +93,7 @@ def boot_active_record .each { |path| require path } rescue LoadError => e # active_record may not be in the Gemfile, so that's okay - puts "Error in TestPlan#boot_active_record: #{e.message}" + puts "Error in TestPlan##{__method__}: #{e.message}" end def boot_rails @@ -113,6 +120,10 @@ def run_rspec_active_record_test run_test_with_libraries('super_diff/rspec', 'super_diff/active_record') end + def run_rspec_action_dispatch_test + run_test_with_libraries('super_diff/rspec', 'super_diff/action_dispatch') + end + def run_rspec_rails_test run_test_with_libraries('super_diff/rspec-rails') end