From 9b80d526c9ec39830696fe103117bd4f15c9a45a Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Mon, 20 Nov 2023 22:33:16 +0000 Subject: [PATCH 1/2] Prevent `stub_template` from leaking between view specs --- .../app/views/_example.html.erb | 1 + example_app_generator/generate_stuff.rb | 2 + .../spec/verify_view_path_stub_spec.rb | 17 ++++++ lib/rspec/rails/example/view_example_group.rb | 2 +- .../rails/example/view_example_group_spec.rb | 58 ++++++++++++++----- 5 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 example_app_generator/app/views/_example.html.erb create mode 100644 example_app_generator/spec/verify_view_path_stub_spec.rb diff --git a/example_app_generator/app/views/_example.html.erb b/example_app_generator/app/views/_example.html.erb new file mode 100644 index 0000000000..683d678ac3 --- /dev/null +++ b/example_app_generator/app/views/_example.html.erb @@ -0,0 +1 @@ +TEMPLATE_HTML diff --git a/example_app_generator/generate_stuff.rb b/example_app_generator/generate_stuff.rb index 9f5c04fd3a..af074104fa 100644 --- a/example_app_generator/generate_stuff.rb +++ b/example_app_generator/generate_stuff.rb @@ -14,10 +14,12 @@ def setup_tasks def final_tasks copy_file 'spec/verify_active_record_spec.rb' + copy_file 'app/views/_example.html.erb' copy_file 'app/views/foo.html' copy_file 'app/views/some_templates/bar.html' copy_file 'spec/verify_custom_renderers_spec.rb' copy_file 'spec/verify_fixture_warning_spec.rb' + copy_file 'spec/verify_view_path_stub_spec.rb' run('bin/rake db:migrate') end diff --git a/example_app_generator/spec/verify_view_path_stub_spec.rb b/example_app_generator/spec/verify_view_path_stub_spec.rb new file mode 100644 index 0000000000..60a9d4110d --- /dev/null +++ b/example_app_generator/spec/verify_view_path_stub_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +RSpec.describe "verify view path doesnt leak stubs between examples", type: :view, order: :defined do + subject(:html) do + render partial: "example" + rendered + end + + it "renders the stub template" do + stub_template("_example.html.erb" => "STUB_HTML") + expect(html).to include("STUB_HTML") + end + + it "renders the file template" do + expect(html).to include("TEMPLATE_HTML") + end +end diff --git a/lib/rspec/rails/example/view_example_group.rb b/lib/rspec/rails/example/view_example_group.rb index 2449c95a30..43eddb927b 100644 --- a/lib/rspec/rails/example/view_example_group.rb +++ b/lib/rspec/rails/example/view_example_group.rb @@ -91,7 +91,7 @@ def view # # stub_template("widgets/_widget.html.erb" => "This content.") def stub_template(hash) - view.view_paths.send(:initialize_copy, ActionView::PathSet.new([StubResolverCache.resolver_for(hash)] + view.view_paths.paths)) + controller.prepend_view_path(StubResolverCache.resolver_for(hash)) end else # Simulates the presence of a template on the file system by adding a diff --git a/spec/rspec/rails/example/view_example_group_spec.rb b/spec/rspec/rails/example/view_example_group_spec.rb index d2a5f86edd..7f6629c7c6 100644 --- a/spec/rspec/rails/example/view_example_group_spec.rb +++ b/spec/rspec/rails/example/view_example_group_spec.rb @@ -280,33 +280,65 @@ def _view; end describe '#stub_template' do let(:view_spec_group) do - Class.new do - include ViewExampleGroup::ExampleMethods - def _view - @_view ||= Struct.new(:view_paths).new(ActionView::PathSet.new(['some-path'])) - end + RSpec.describe "a view spec" do + include ::RSpec::Rails::ViewExampleGroup end end it 'prepends an ActionView::FixtureResolver to the view path' do - view_spec = view_spec_group.new - view_spec.stub_template('some_path/some_template' => 'stubbed-contents') + result = :not_loaded - result = view_spec.view.view_paths.first + view_spec_group.specify do + stub_template('some_path/some_template' => 'stubbed-contents') + result = view.view_paths.first + end + view_spec_group.run expect(result).to be_instance_of(ActionView::FixtureResolver) data = result.respond_to?(:data) ? result.data : result.hash expect(data).to eq('some_path/some_template' => 'stubbed-contents') end + it 'caches FixtureResolver instances between examples' do + example_one_view_paths = :not_set + example_two_view_paths = :not_set + + view_spec_group.specify do + stub_template('some_path/some_template' => 'stubbed-contents') + example_one_view_paths = view.view_paths + end + view_spec_group.specify do + stub_template('some_path/some_template' => 'stubbed-contents') + example_two_view_paths = view.view_paths + end + view_spec_group.run + + expect(example_one_view_paths.first).to eq(example_two_view_paths.first) + end + it 'caches FixtureResolver instances between example groups' do - view_spec_one = view_spec_group.new - view_spec_two = view_spec_group.new + example_one_view_paths = :not_set + example_two_view_paths = :not_set - view_spec_one.stub_template('some_path/some_template' => 'stubbed-contents') - view_spec_two.stub_template('some_path/some_template' => 'stubbed-contents') + RSpec.describe "a view spec" do + include ::RSpec::Rails::ViewExampleGroup + + specify do + stub_template('some_path/some_template' => 'stubbed-contents') + example_one_view_paths = view.view_paths + end + end.run + + RSpec.describe "another view spec" do + include ::RSpec::Rails::ViewExampleGroup + + specify do + stub_template('some_path/some_template' => 'stubbed-contents') + example_two_view_paths = view.view_paths + end + end.run - expect(view_spec_one.view.view_paths.first).to eq(view_spec_two.view.view_paths.first) + expect(example_one_view_paths.first).to eq(example_two_view_paths.first) end end end From 52898cebb233d5d6f5f8f4c3b3d40b16bc6d04c8 Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Tue, 21 Nov 2023 08:46:28 +0000 Subject: [PATCH 2/2] Use preprend_view_path on all versions of Rails --- lib/rspec/rails/example/view_example_group.rb | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/lib/rspec/rails/example/view_example_group.rb b/lib/rspec/rails/example/view_example_group.rb index 43eddb927b..a03b19958d 100644 --- a/lib/rspec/rails/example/view_example_group.rb +++ b/lib/rspec/rails/example/view_example_group.rb @@ -83,26 +83,14 @@ def view _view end - if ::Rails.version.to_f >= 7.1 - # Simulates the presence of a template on the file system by adding a - # Rails' FixtureResolver to the front of the view_paths list. Designed to - # help isolate view examples from partials rendered by the view template - # that is the subject of the example. - # - # stub_template("widgets/_widget.html.erb" => "This content.") - def stub_template(hash) - controller.prepend_view_path(StubResolverCache.resolver_for(hash)) - end - else - # Simulates the presence of a template on the file system by adding a - # Rails' FixtureResolver to the front of the view_paths list. Designed to - # help isolate view examples from partials rendered by the view template - # that is the subject of the example. - # - # stub_template("widgets/_widget.html.erb" => "This content.") - def stub_template(hash) - view.view_paths.unshift(StubResolverCache.resolver_for(hash)) - end + # Simulates the presence of a template on the file system by adding a + # Rails' FixtureResolver to the front of the view_paths list. Designed to + # help isolate view examples from partials rendered by the view template + # that is the subject of the example. + # + # stub_template("widgets/_widget.html.erb" => "This content.") + def stub_template(hash) + controller.prepend_view_path(StubResolverCache.resolver_for(hash)) end # Provides access to the params hash that will be available within the