From a0dc958c84f427186ac9f42ae14fb0ed166abe1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADt=20Ondruch?= Date: Mon, 16 Dec 2024 18:09:31 +0100 Subject: [PATCH 1/5] Fixes for Ruby 3.4 new `Hash#inspect` syntax This fix prefers the new Ruby 3.4 formatting then keeping the output backward compatibility. Fixes: #2820 [1]: https://bugs.ruby-lang.org/issues/20433 [2]: https://github.com/rspec/rspec/blob/60a7a65e195953196fb8d1836257909c56d2da85/rspec-expectations/lib/rspec/matchers/composable.rb#L162 --- .github/workflows/ci.yml | 3 + .../action_cable/have_broadcasted_to_spec.rb | 10 +++- spec/rspec/rails/matchers/be_a_new_spec.rb | 58 ++++++++++++++----- spec/rspec/rails/matchers/be_routable_spec.rb | 20 ++++++- spec/rspec/rails/matchers/route_to_spec.rb | 19 +++++- 5 files changed, 90 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b0473130f..daeb88792e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,9 @@ jobs: RAILS_VERSION: 'main' # Rails 8.0 builds >= 3.2 + - ruby: 3.4.0-rc1 + env: + RAILS_VERSION: '~> 8.0.0' - ruby: 3.3 env: RAILS_VERSION: '~> 8.0.0' diff --git a/spec/rspec/rails/matchers/action_cable/have_broadcasted_to_spec.rb b/spec/rspec/rails/matchers/action_cable/have_broadcasted_to_spec.rb index 67d4a51c78..fb37fec131 100644 --- a/spec/rspec/rails/matchers/action_cable/have_broadcasted_to_spec.rb +++ b/spec/rspec/rails/matchers/action_cable/have_broadcasted_to_spec.rb @@ -248,12 +248,16 @@ def broadcast(stream, msg) end it "has an appropriate description including the matcher's description when qualified with `#with` and a composable matcher" do - expect( - have_broadcasted_to("my_stream") + description = have_broadcasted_to("my_stream") .from_channel(channel) .with(a_hash_including(a: :b)) .description - ).to eq("have broadcasted exactly 1 messages to my_stream with a hash including {:a => :b}") + + if RUBY_VERSION >= '3.4' + expect(description).to eq("have broadcasted exactly 1 messages to my_stream with a hash including {a: :b}") + else + expect(description).to eq("have broadcasted exactly 1 messages to my_stream with a hash including {:a => :b}") + end end end end diff --git a/spec/rspec/rails/matchers/be_a_new_spec.rb b/spec/rspec/rails/matchers/be_a_new_spec.rb index 76906a56fc..d24e3c699c 100644 --- a/spec/rspec/rails/matchers/be_a_new_spec.rb +++ b/spec/rspec/rails/matchers/be_a_new_spec.rb @@ -71,10 +71,16 @@ def new_record?; true; end end it "fails" do + message = + if RUBY_VERSION >= '3.4' + "attribute {\"foo\" => (a string matching \"bar\")} was not set on #{record.inspect}" + else + "attribute {\"foo\"=>(a string matching \"bar\")} was not set on #{record.inspect}" + end expect { expect(record).to be_a_new(record.class).with( foo: a_string_matching("bar")) - }.to raise_error("attribute {\"foo\"=>(a string matching \"bar\")} was not set on #{record.inspect}") + }.to raise_error(message) end context "matcher is wrong type" do @@ -101,12 +107,18 @@ def new_record?; true; end context "only one matcher present in actual" do it "fails" do + message = + if RUBY_VERSION >= '3.4' + "attribute {\"bar\" => (a string matching \"barn\")} was not set on #{record.inspect}" + else + "attribute {\"bar\"=>(a string matching \"barn\")} was not set on #{record.inspect}" + end expect { expect(record).to be_a_new(record.class).with( foo: a_string_matching("foo"), bar: a_string_matching("barn") ) - }.to raise_error("attribute {\"bar\"=>(a string matching \"barn\")} was not set on #{record.inspect}") + }.to raise_error(message) end end end @@ -118,19 +130,29 @@ def new_record?; true; end expect(record).to be_a_new(record.class).with(zoo: 'zoo', car: 'car') }.to raise_error { |e| expect(e.message).to match(/attributes \{.*\} were not set on #{Regexp.escape record.inspect}/) - expect(e.message).to match(/"zoo"=>"zoo"/) - expect(e.message).to match(/"car"=>"car"/) + if RUBY_VERSION >= '3.4' + expect(e.message).to match(/"zoo" => "zoo"/) + expect(e.message).to match(/"car" => "car"/) + else + expect(e.message).to match(/"zoo"=>"zoo"/) + expect(e.message).to match(/"car"=>"car"/) + end } end end context "one attribute value not the same" do it "fails" do + message = + if RUBY_VERSION >= '3.4' + %(attribute {"foo" => "bar"} was not set on #{record.inspect}) + else + %(attribute {"foo"=>"bar"} was not set on #{record.inspect}) + end + expect { expect(record).to be_a_new(record.class).with(foo: 'bar') - }.to raise_error( - %(attribute {"foo"=>"bar"} was not set on #{record.inspect}) - ) + }.to raise_error(message) end end end @@ -166,20 +188,30 @@ def new_record?; false; end expect(record).to be_a_new(String).with(zoo: 'zoo', car: 'car') }.to raise_error { |e| expect(e.message).to match(/expected #{Regexp.escape record.inspect} to be a new String and attributes \{.*\} were not set on #{Regexp.escape record.inspect}/) - expect(e.message).to match(/"zoo"=>"zoo"/) - expect(e.message).to match(/"car"=>"car"/) + if RUBY_VERSION >= '3.4' + expect(e.message).to match(/"zoo" => "zoo"/) + expect(e.message).to match(/"car" => "car"/) + else + expect(e.message).to match(/"zoo"=>"zoo"/) + expect(e.message).to match(/"car"=>"car"/) + end } end end context "one attribute value not the same" do it "fails" do + message = + "expected #{record.inspect} to be a new String and " + + if RUBY_VERSION >= '3.4' + %(attribute {"foo" => "bar"} was not set on #{record.inspect}) + else + %(attribute {"foo"=>"bar"} was not set on #{record.inspect}) + end + expect { expect(record).to be_a_new(String).with(foo: 'bar') - }.to raise_error( - "expected #{record.inspect} to be a new String and " + - %(attribute {"foo"=>"bar"} was not set on #{record.inspect}) - ) + }.to raise_error(message) end end end diff --git a/spec/rspec/rails/matchers/be_routable_spec.rb b/spec/rspec/rails/matchers/be_routable_spec.rb index c3a6a1b158..65edb2bb50 100644 --- a/spec/rspec/rails/matchers/be_routable_spec.rb +++ b/spec/rspec/rails/matchers/be_routable_spec.rb @@ -18,9 +18,17 @@ it "fails if routes do not recognize the path" do allow(routes).to receive(:recognize_path) { raise ActionController::RoutingError, 'ignore' } + + message = + if RUBY_VERSION >= '3.4' + /expected \{get: "\/a\/path"\} to be routable/ + else + /expected \{:get=>"\/a\/path"\} to be routable/ + end + expect do expect({ get: "/a/path" }).to be_routable - end.to raise_error(/expected \{:get=>"\/a\/path"\} to be routable/) + end.to raise_error(message) end end @@ -35,9 +43,17 @@ it "fails if routes recognize the path" do allow(routes).to receive(:recognize_path) { { controller: "foo" } } + + message = + if RUBY_VERSION >= '3.4' + /expected \{get: "\/a\/path"\} not to be routable, but it routes to \{controller: "foo"\}/ + else + /expected \{:get=>"\/a\/path"\} not to be routable, but it routes to \{:controller=>"foo"\}/ + end + expect do expect({ get: "/a/path" }).not_to be_routable - end.to raise_error(/expected \{:get=>"\/a\/path"\} not to be routable, but it routes to \{:controller=>"foo"\}/) + end.to raise_error(message) end end end diff --git a/spec/rspec/rails/matchers/route_to_spec.rb b/spec/rspec/rails/matchers/route_to_spec.rb index ae49791f8a..8b1d7836bb 100644 --- a/spec/rspec/rails/matchers/route_to_spec.rb +++ b/spec/rspec/rails/matchers/route_to_spec.rb @@ -9,7 +9,15 @@ def assert_recognizes(*) it "provides a description" do matcher = route_to("these" => "options") matcher.matches?(get: "path") - expect(matcher.description).to eq("route {:get=>\"path\"} to {\"these\"=>\"options\"}") + + description = + if RUBY_VERSION >= '3.4' + "route {get: \"path\"} to {\"these\" => \"options\"}" + else + "route {:get=>\"path\"} to {\"these\"=>\"options\"}" + end + + expect(matcher.description).to eq(description) end it "delegates to assert_recognizes" do @@ -107,9 +115,16 @@ def assert_recognizes(*) context "with should_not" do context "when assert_recognizes passes" do it "fails with custom message" do + message = + if RUBY_VERSION >= '3.4' + /expected \{get: "path"\} not to route to \{"these" => "options"\}/ + else + /expected \{:get=>"path"\} not to route to \{"these"=>"options"\}/ + end + expect { expect({ get: "path" }).not_to route_to("these" => "options") - }.to raise_error(/expected \{:get=>"path"\} not to route to \{"these"=>"options"\}/) + }.to raise_error(message) end end From 11b5ceddb6b5d4b53e058bd8b95b1c32b0f4a390 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Fri, 20 Dec 2024 12:07:53 +0100 Subject: [PATCH 2/5] Make #unmatching_mail_jobs working with frozen warning in Ruby 3.4 --- lib/rspec/rails/matchers/have_enqueued_mail.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rspec/rails/matchers/have_enqueued_mail.rb b/lib/rspec/rails/matchers/have_enqueued_mail.rb index cf4576e574..14f8f01112 100644 --- a/lib/rspec/rails/matchers/have_enqueued_mail.rb +++ b/lib/rspec/rails/matchers/have_enqueued_mail.rb @@ -129,13 +129,13 @@ def unmatching_mail_jobs end def unmatching_mail_jobs_message - msg = "Queued deliveries:" + messages = ["Queued deliveries:"] unmatching_mail_jobs.each do |job| - msg << "\n #{mail_job_message(job)}" + messages << " #{mail_job_message(job)}" end - msg + messages.join("\n") end def mail_job_message(job) From ccb5b3c5cd05f59ef4dd1f7476362ed8df37d951 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Fri, 20 Dec 2024 12:42:59 +0100 Subject: [PATCH 3/5] Properly match new error representation without backtick --- spec/sanity_check_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/sanity_check_spec.rb b/spec/sanity_check_spec.rb index 4746a157e6..7faef98a87 100644 --- a/spec/sanity_check_spec.rb +++ b/spec/sanity_check_spec.rb @@ -29,6 +29,7 @@ def with_clean_env .to match(/uninitialized constant RSpec::Support/) .or match(/undefined method `require_rspec_core' for RSpec::Support:Module/) .or match(/undefined method `require_rspec_core' for module RSpec::Support/) + .or match(/undefined method 'require_rspec_core' for module RSpec::Support/) expect($?.exitstatus).to eq(1) end From 65d2eb20602be5c6b04652bfc944f7ed4a40e539 Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Sat, 21 Dec 2024 12:36:28 +0000 Subject: [PATCH 4/5] Relax cucumber to allow updated verson on Ruby 3.4 --- rspec-rails.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rspec-rails.gemspec b/rspec-rails.gemspec index 788977e11c..dd8e24ea68 100644 --- a/rspec-rails.gemspec +++ b/rspec-rails.gemspec @@ -55,5 +55,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'ammeter', '~> 1.1.5' s.add_development_dependency 'aruba', '~> 0.14.12' - s.add_development_dependency 'cucumber', '~> 7.0' + s.add_development_dependency 'cucumber', '> 7.0' end From daae082010c3bc26fcd5efbe5e791c150a3a8a9d Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Sat, 21 Dec 2024 12:50:34 +0000 Subject: [PATCH 5/5] Try cucumber from head --- Gemfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index 5df5cc6e41..609895b364 100644 --- a/Gemfile +++ b/Gemfile @@ -16,6 +16,10 @@ gem 'ffi', '> 1.15.5' gem 'rake', '> 12' gem 'rubocop', '~> 1.28.2' +if RUBY_VERSION.to_f > 3.3 + gem 'cucumber', git: 'https://github.com/cucumber/cucumber-ruby', branch: 'main' +end + custom_gemfile = File.expand_path('Gemfile-custom', __dir__) eval_gemfile custom_gemfile if File.exist?(custom_gemfile)