diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cdd1d56d7..5ab482519 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,31 +29,10 @@ jobs: fail-fast: false matrix: include: - # Edge Rails (7.1) builds >= 2.7 - - ruby: '3.0' - allow_failure: true - env: - RAILS_VERSION: 'main' - - ruby: 2.7 - allow_failure: true - env: - RAILS_VERSION: 'main' - - # Rails 7.0 builds >= 2.7 - - ruby: 3.1 - allow_failure: true - env: - RAILS_VERSION: '~> 7.0.0' - - ruby: '3.0' - allow_failure: true - env: - RAILS_VERSION: '~> 7.0.0' - - ruby: 2.7 - allow_failure: true - env: - RAILS_VERSION: '~> 7.0.0' - # Rails 6.1 builds >= 2.5 + - ruby: '3.1' + env: + RAILS_VERSION: '~> 6.1.0' - ruby: '3.0' env: RAILS_VERSION: '~> 6.1.0' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8221da291..19abcfbf0 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -9,3 +9,7 @@ Layout/LineLength: # Over time we'd like to get this down, but this is what we're at now. Metrics/MethodLength: Max: 43 # default: 10 + +Metrics/ClassLength: + Exclude: + - lib/rspec/rails/matchers/have_enqueued_mail.rb diff --git a/lib/rspec/rails/feature_check.rb b/lib/rspec/rails/feature_check.rb index 2570d6659..11558046e 100644 --- a/lib/rspec/rails/feature_check.rb +++ b/lib/rspec/rails/feature_check.rb @@ -39,6 +39,10 @@ def has_action_mailbox? defined?(::ActionMailbox) end + def ruby_3_1? + RUBY_VERSION >= "3.1" + end + def type_metatag(type) "type: :#{type}" end diff --git a/lib/rspec/rails/matchers/have_enqueued_mail.rb b/lib/rspec/rails/matchers/have_enqueued_mail.rb index b7756346e..2614631d4 100644 --- a/lib/rspec/rails/matchers/have_enqueued_mail.rb +++ b/lib/rspec/rails/matchers/have_enqueued_mail.rb @@ -76,7 +76,7 @@ def job_match?(job) def arguments_match?(job) @args = if @mail_args.any? - base_mailer_args + @mail_args + base_mailer_args + process_arguments(job, @mail_args) elsif @mailer_class && @method_name base_mailer_args + [any_args] elsif @mailer_class @@ -88,12 +88,38 @@ def arguments_match?(job) super(job) end + def process_arguments(job, given_mail_args) + # Old matcher behavior working with all builtin classes but ActionMailer::MailDeliveryJob + return given_mail_args if use_given_mail_args?(job) + + # If matching args starts with a hash and job instance has params match with them + if given_mail_args.first.is_a?(Hash) && job[:args][3]['params'].present? + [hash_including(params: given_mail_args[0], args: given_mail_args.drop(1))] + else + [hash_including(args: given_mail_args)] + end + end + + def use_given_mail_args?(job) + return true if FeatureCheck.has_action_mailer_parameterized? && job[:job] <= ActionMailer::Parameterized::DeliveryJob + return false if FeatureCheck.ruby_3_1? + + !(FeatureCheck.has_action_mailer_unified_delivery? && job[:job] <= ActionMailer::MailDeliveryJob) + end + def base_mailer_args [mailer_class_name, @method_name.to_s, MAILER_JOB_METHOD] end def yield_mail_args(block) - proc { |*job_args| block.call(*(job_args - base_mailer_args)) } + proc do |*job_args| + mailer_args = job_args - base_mailer_args + if mailer_args.first.is_a?(Hash) + block.call(*mailer_args.first[:args]) + else + block.call(*mailer_args) + end + end end def check_active_job_adapter @@ -120,16 +146,23 @@ def unmatching_mail_jobs_message def mail_job_message(job) mailer_method = job[:args][0..1].join('.') - - mailer_args = job[:args][3..-1] + mailer_args = deserialize_arguments(job)[3..-1] + mailer_args = mailer_args.first[:args] if unified_mail?(job) msg_parts = [] - msg_parts << "with #{mailer_args}" if mailer_args.any? + display_args = display_mailer_args(mailer_args) + msg_parts << "with #{display_args}" if display_args.any? msg_parts << "on queue #{job[:queue]}" if job[:queue] && job[:queue] != 'mailers' msg_parts << "at #{Time.at(job[:at])}" if job[:at] "#{mailer_method} #{msg_parts.join(', ')}".strip end + def display_mailer_args(mailer_args) + return mailer_args unless mailer_args.first.is_a?(Hash) && mailer_args.first.key?(:args) + + mailer_args.first[:args] + end + def legacy_mail?(job) job[:job] <= ActionMailer::DeliveryJob end diff --git a/spec/rspec/rails/matchers/have_enqueued_mail_spec.rb b/spec/rspec/rails/matchers/have_enqueued_mail_spec.rb index 152e02fff..119779352 100644 --- a/spec/rspec/rails/matchers/have_enqueued_mail_spec.rb +++ b/spec/rspec/rails/matchers/have_enqueued_mail_spec.rb @@ -393,18 +393,22 @@ def self.name; "NonMailerJob"; end }.to have_enqueued_mail(UnifiedMailer, :test_email).and have_enqueued_mail(UnifiedMailer, :email_with_args) end - it "passes with provided argument matchers" do + it "matches arguments when mailer has only args" do + expect { + UnifiedMailer.email_with_args(1, 2).deliver_later + }.to have_enqueued_mail(UnifiedMailer, :email_with_args).with(1, 2) + end + + it "matches arguments when mailer is parameterized" do expect { UnifiedMailer.with('foo' => 'bar').test_email.deliver_later - }.to have_enqueued_mail(UnifiedMailer, :test_email).with( - a_hash_including(params: {'foo' => 'bar'}) - ) + }.to have_enqueued_mail(UnifiedMailer, :test_email).with('foo' => 'bar') + end + it "matches arguments when mixing parameterized and non-parameterized emails" do expect { UnifiedMailer.with('foo' => 'bar').email_with_args(1, 2).deliver_later - }.to have_enqueued_mail(UnifiedMailer, :email_with_args).with( - a_hash_including(params: {'foo' => 'bar'}, args: [1, 2]) - ) + }.to have_enqueued_mail(UnifiedMailer, :email_with_args).with({'foo' => 'bar'}, 1, 2) end it "passes when using a mailer with `delivery_job` set to a sub class of `ActionMailer::DeliveryJob`" do