actionmailer 7.0.8 → 7.1.3.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionmailer might be problematic. Click here for more details.

@@ -3,6 +3,8 @@
3
3
  require "tmpdir"
4
4
 
5
5
  module ActionMailer
6
+ # = Action Mailer \DeliveryMethods
7
+ #
6
8
  # This module handles everything related to mail delivery, from registering
7
9
  # new delivery methods to configuring the mail object to be sent.
8
10
  module DeliveryMethods
@@ -12,7 +14,6 @@ module ActionMailer
12
14
  # Do not make this inheritable, because we always want it to propagate
13
15
  cattr_accessor :raise_delivery_errors, default: true
14
16
  cattr_accessor :perform_deliveries, default: true
15
- cattr_accessor :deliver_later_queue_name, default: :mailers
16
17
 
17
18
  class_attribute :delivery_methods, default: {}.freeze
18
19
  class_attribute :delivery_method, default: :smtp
@@ -31,7 +32,8 @@ module ActionMailer
31
32
 
32
33
  add_delivery_method :sendmail, Mail::Sendmail,
33
34
  location: "/usr/sbin/sendmail",
34
- arguments: "-i"
35
+ # See breaking change in the mail gem - https://github.com/mikel/mail/commit/7e1196bd29815a0901d7290c82a332c0959b163a
36
+ arguments: Gem::Version.new(Mail::VERSION.version) >= Gem::Version.new("2.8.0") ? %w[-i] : "-i"
35
37
 
36
38
  add_delivery_method :test, Mail::TestMailer
37
39
  end
@@ -46,7 +48,7 @@ module ActionMailer
46
48
  #
47
49
  # add_delivery_method :sendmail, Mail::Sendmail,
48
50
  # location: '/usr/sbin/sendmail',
49
- # arguments: '-i'
51
+ # arguments: %w[ -i ]
50
52
  def add_delivery_method(symbol, klass, default_options = {})
51
53
  class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings")
52
54
  public_send(:"#{symbol}_settings=", default_options)
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMailer
4
+ def self.deprecator # :nodoc:
5
+ @deprecator ||= ActiveSupport::Deprecation.new
6
+ end
7
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMailer
4
+ # = Action Mailer Form Builder
5
+ #
6
+ # Override the default form builder for all views rendered by this
7
+ # mailer and any of its descendants. Accepts a subclass of
8
+ # ActionView::Helpers::FormBuilder.
9
+ #
10
+ # While emails typically will not include forms, this can be used
11
+ # by views that are shared between controllers and mailers.
12
+ #
13
+ # For more information, see +ActionController::FormBuilder+.
14
+ module FormBuilder
15
+ extend ActiveSupport::Concern
16
+
17
+ included do
18
+ class_attribute :_default_form_builder, instance_accessor: false
19
+ end
20
+
21
+ module ClassMethods
22
+ # Set the form builder to be used as the default for all forms
23
+ # in the views rendered by this mailer and its subclasses.
24
+ #
25
+ # ==== Parameters
26
+ # * <tt>builder</tt> - Default form builder, an instance of ActionView::Helpers::FormBuilder
27
+ def default_form_builder(builder)
28
+ self._default_form_builder = builder
29
+ end
30
+ end
31
+
32
+ # Default form builder for the mailer
33
+ def default_form_builder
34
+ self.class._default_form_builder
35
+ end
36
+ end
37
+ end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionMailer
4
- # Returns the currently loaded version of Action Mailer as a <tt>Gem::Version</tt>.
4
+ # Returns the currently loaded version of Action Mailer as a +Gem::Version+.
5
5
  def self.gem_version
6
6
  Gem::Version.new VERSION::STRING
7
7
  end
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 7
11
- MINOR = 0
12
- TINY = 8
13
- PRE = nil
11
+ MINOR = 1
12
+ TINY = 3
13
+ PRE = "2"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -3,6 +3,8 @@
3
3
  require "base64"
4
4
 
5
5
  module ActionMailer
6
+ # = Action Mailer \InlinePreviewInterceptor
7
+ #
6
8
  # Implements a mailer preview interceptor that converts image tag src attributes
7
9
  # that use inline cid: style URLs to data: style URLs so that they are visible
8
10
  # when previewing an HTML email in a web browser.
@@ -3,14 +3,17 @@
3
3
  require "active_support/log_subscriber"
4
4
 
5
5
  module ActionMailer
6
+ # = Action Mailer \LogSubscriber
7
+ #
6
8
  # Implements the ActiveSupport::LogSubscriber for logging notifications when
7
9
  # email is delivered or received.
8
10
  class LogSubscriber < ActiveSupport::LogSubscriber
9
11
  # An email was delivered.
10
12
  def deliver(event)
11
13
  info do
12
- perform_deliveries = event.payload[:perform_deliveries]
13
- if perform_deliveries
14
+ if exception = event.payload[:exception_object]
15
+ "Failed delivery of mail #{event.payload[:message_id]} error_class=#{exception.class} error_message=#{exception.message.inspect}"
16
+ elsif event.payload[:perform_deliveries]
14
17
  "Delivered mail #{event.payload[:message_id]} (#{event.duration.round(1)}ms)"
15
18
  else
16
19
  "Skipped delivery of mail #{event.payload[:message_id]} as `perform_deliveries` is false"
@@ -19,6 +22,7 @@ module ActionMailer
19
22
 
20
23
  debug { event.payload[:mail] }
21
24
  end
25
+ subscribe_log_level :deliver, :debug
22
26
 
23
27
  # An email was generated.
24
28
  def process(event)
@@ -28,6 +32,7 @@ module ActionMailer
28
32
  "#{mailer}##{action}: processed outbound mail in #{event.duration.round(1)}ms"
29
33
  end
30
34
  end
35
+ subscribe_log_level :process, :debug
31
36
 
32
37
  # Use the logger configured for ActionMailer::Base.
33
38
  def logger
@@ -3,13 +3,18 @@
3
3
  require "active_job"
4
4
 
5
5
  module ActionMailer
6
- # The <tt>ActionMailer::MailDeliveryJob</tt> class is used when you
6
+ # = Action Mailer \MailDeliveryJob
7
+ #
8
+ # The +ActionMailer::MailDeliveryJob+ class is used when you
7
9
  # want to send emails outside of the request-response cycle. It supports
8
10
  # sending either parameterized or normal mail.
9
11
  #
10
12
  # Exceptions are rescued and handled by the mailer class.
11
13
  class MailDeliveryJob < ActiveJob::Base # :nodoc:
12
- queue_as { ActionMailer::Base.deliver_later_queue_name }
14
+ queue_as do
15
+ mailer_class = arguments.first.constantize
16
+ mailer_class.deliver_later_queue_name
17
+ end
13
18
 
14
19
  rescue_from StandardError, with: :handle_exception_with_mailer_class
15
20
 
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionMailer
4
+ # = Action Mailer \MailHelper
5
+ #
4
6
  # Provides helper methods for ActionMailer::Base that can be used for easily
5
7
  # formatting messages, accessing mailer or message instances, and the
6
8
  # attachments list.
@@ -3,11 +3,13 @@
3
3
  require "delegate"
4
4
 
5
5
  module ActionMailer
6
- # The <tt>ActionMailer::MessageDelivery</tt> class is used by
6
+ # = Action Mailer \MessageDelivery
7
+ #
8
+ # The +ActionMailer::MessageDelivery+ class is used by
7
9
  # ActionMailer::Base when creating a new mailer.
8
10
  # <tt>MessageDelivery</tt> is a wrapper (+Delegator+ subclass) around a lazy
9
- # created <tt>Mail::Message</tt>. You can get direct access to the
10
- # <tt>Mail::Message</tt>, deliver the email or schedule the email to be sent
11
+ # created +Mail::Message+. You can get direct access to the
12
+ # +Mail::Message+, deliver the email or schedule the email to be sent
11
13
  # through Active Job.
12
14
  #
13
15
  # Notifier.welcome(User.first) # an ActionMailer::MessageDelivery object
@@ -62,9 +64,10 @@ module ActionMailer
62
64
  # * <tt>:queue</tt> - Enqueue the email on the specified queue
63
65
  # * <tt>:priority</tt> - Enqueues the email with the specified priority
64
66
  #
65
- # By default, the email will be enqueued using <tt>ActionMailer::MailDeliveryJob</tt>. Each
66
- # ActionMailer::Base class can specify the job to use by setting the class variable
67
- # +delivery_job+.
67
+ # By default, the email will be enqueued using ActionMailer::MailDeliveryJob on
68
+ # the default queue. Mailer classes can customize the queue name used for the default
69
+ # job by assigning a +deliver_later_queue_name+ class variable, or provide a custom job
70
+ # by assigning a +delivery_job+. When a custom job is used, it controls the queue name.
68
71
  #
69
72
  # class AccountRegistrationMailer < ApplicationMailer
70
73
  # self.delivery_job = RegistrationDeliveryJob
@@ -88,9 +91,10 @@ module ActionMailer
88
91
  # * <tt>:queue</tt> - Enqueue the email on the specified queue.
89
92
  # * <tt>:priority</tt> - Enqueues the email with the specified priority
90
93
  #
91
- # By default, the email will be enqueued using <tt>ActionMailer::MailDeliveryJob</tt>. Each
92
- # ActionMailer::Base class can specify the job to use by setting the class variable
93
- # +delivery_job+.
94
+ # By default, the email will be enqueued using ActionMailer::MailDeliveryJob on
95
+ # the default queue. Mailer classes can customize the queue name used for the default
96
+ # job by assigning a +deliver_later_queue_name+ class variable, or provide a custom job
97
+ # by assigning a +delivery_job+. When a custom job is used, it controls the queue name.
94
98
  #
95
99
  # class AccountRegistrationMailer < ApplicationMailer
96
100
  # self.delivery_job = RegistrationDeliveryJob
@@ -106,7 +110,9 @@ module ActionMailer
106
110
  #
107
111
  def deliver_now!
108
112
  processed_mailer.handle_exceptions do
109
- message.deliver!
113
+ processed_mailer.run_callbacks(:deliver) do
114
+ message.deliver!
115
+ end
110
116
  end
111
117
  end
112
118
 
@@ -116,13 +122,15 @@ module ActionMailer
116
122
  #
117
123
  def deliver_now
118
124
  processed_mailer.handle_exceptions do
119
- message.deliver
125
+ processed_mailer.run_callbacks(:deliver) do
126
+ message.deliver
127
+ end
120
128
  end
121
129
  end
122
130
 
123
131
  private
124
132
  # Returns the processed Mailer instance. We keep this instance
125
- # on hand so we can delegate exception handling to it.
133
+ # on hand so we can run callbacks and delegate exception handling to it.
126
134
  def processed_mailer
127
135
  @processed_mailer ||= @mailer_class.new.tap do |mailer|
128
136
  mailer.process @action, *@args
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionMailer
4
+ # = Action Mailer \Parameterized
5
+ #
4
6
  # Provides the option to parameterize mailers in order to share instance variable
5
7
  # setup, processing, and common headers.
6
8
  #
@@ -88,7 +90,11 @@ module ActionMailer
88
90
  extend ActiveSupport::Concern
89
91
 
90
92
  included do
91
- attr_accessor :params
93
+ attr_writer :params
94
+
95
+ def params
96
+ @params ||= {}
97
+ end
92
98
  end
93
99
 
94
100
  module ClassMethods
@@ -7,11 +7,11 @@ module ActionMailer
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
- # Set the location of mailer previews through app configuration:
10
+ # Add the location of mailer previews through app configuration:
11
11
  #
12
- # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
12
+ # config.action_mailer.preview_paths << "#{Rails.root}/lib/mailer_previews"
13
13
  #
14
- mattr_accessor :preview_path, instance_writer: false
14
+ mattr_accessor :preview_paths, instance_writer: false, default: []
15
15
 
16
16
  # Enable or disable mailer previews through app configuration:
17
17
  #
@@ -25,7 +25,31 @@ module ActionMailer
25
25
  mattr_accessor :preview_interceptors, instance_writer: false, default: [ActionMailer::InlinePreviewInterceptor]
26
26
  end
27
27
 
28
+ def preview_path
29
+ ActionMailer.deprecator.warn(<<-MSG.squish)
30
+ Using preview_path option is deprecated and will be removed in Rails 7.2.
31
+ Please use preview_paths instead.
32
+ MSG
33
+ self.class.preview_paths.first
34
+ end
35
+
28
36
  module ClassMethods
37
+ def preview_path=(value)
38
+ ActionMailer.deprecator.warn(<<-MSG.squish)
39
+ Using preview_path= option is deprecated and will be removed in Rails 7.2.
40
+ Please use preview_paths= instead.
41
+ MSG
42
+ self.preview_paths << value
43
+ end
44
+
45
+ def preview_path
46
+ ActionMailer.deprecator.warn(<<-MSG.squish)
47
+ Using preview_path option is deprecated and will be removed in Rails 7.2.
48
+ Please use preview_paths instead.
49
+ MSG
50
+ self.preview_paths.first
51
+ end
52
+
29
53
  # Register one or more Interceptors which will be called before mail is previewed.
30
54
  def register_preview_interceptors(*interceptors)
31
55
  interceptors.flatten.compact.each { |interceptor| register_preview_interceptor(interceptor) }
@@ -79,7 +103,7 @@ module ActionMailer
79
103
  # Returns all mailer preview classes.
80
104
  def all
81
105
  load_previews if descendants.empty?
82
- descendants
106
+ descendants.sort_by { |mailer| mailer.name.titleize }
83
107
  end
84
108
 
85
109
  # Returns the mail object for the given email name. The registered preview
@@ -119,13 +143,13 @@ module ActionMailer
119
143
 
120
144
  private
121
145
  def load_previews
122
- if preview_path
123
- Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file }
146
+ preview_paths.each do |preview_path|
147
+ Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require file }
124
148
  end
125
149
  end
126
150
 
127
- def preview_path
128
- Base.preview_path
151
+ def preview_paths
152
+ Base.preview_paths
129
153
  end
130
154
 
131
155
  def show_previews
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMailer
4
+ module QueuedDelivery
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :delivery_job, default: ::ActionMailer::MailDeliveryJob
9
+ class_attribute :deliver_later_queue_name, default: :mailers
10
+ end
11
+ end
12
+ end
@@ -8,8 +8,13 @@ require "abstract_controller/railties/routes_helpers"
8
8
  module ActionMailer
9
9
  class Railtie < Rails::Railtie # :nodoc:
10
10
  config.action_mailer = ActiveSupport::OrderedOptions.new
11
+ config.action_mailer.preview_paths = []
11
12
  config.eager_load_namespaces << ActionMailer
12
13
 
14
+ initializer "action_mailer.deprecator", before: :load_environment_config do |app|
15
+ app.deprecators[:action_mailer] = ActionMailer.deprecator
16
+ end
17
+
13
18
  initializer "action_mailer.logger" do
14
19
  ActiveSupport.on_load(:action_mailer) { self.logger ||= Rails.logger }
15
20
  end
@@ -23,10 +28,7 @@ module ActionMailer
23
28
  options.stylesheets_dir ||= paths["public/stylesheets"].first
24
29
  options.show_previews = Rails.env.development? if options.show_previews.nil?
25
30
  options.cache_store ||= Rails.cache
26
-
27
- if options.show_previews
28
- options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/mailers/previews" : nil
29
- end
31
+ options.preview_paths |= ["#{Rails.root}/test/mailers/previews"]
30
32
 
31
33
  # make sure readers methods get compiled
32
34
  options.asset_host ||= app.config.asset_host
@@ -40,6 +42,7 @@ module ActionMailer
40
42
  register_interceptors(options.delete(:interceptors))
41
43
  register_preview_interceptors(options.delete(:preview_interceptors))
42
44
  register_observers(options.delete(:observers))
45
+ self.preview_paths |= options[:preview_paths]
43
46
 
44
47
  if delivery_job = options.delete(:delivery_job)
45
48
  self.delivery_job = delivery_job.constantize
@@ -65,12 +68,9 @@ module ActionMailer
65
68
  end
66
69
  end
67
70
 
68
- initializer "action_mailer.set_autoload_paths" do |app|
71
+ initializer "action_mailer.set_autoload_paths", before: :set_autoload_paths do |app|
69
72
  options = app.config.action_mailer
70
-
71
- if options.show_previews && options.preview_path
72
- ActiveSupport::Dependencies.autoload_paths << options.preview_path
73
- end
73
+ app.config.paths["test/mailers/previews"].concat(options.preview_paths)
74
74
  end
75
75
 
76
76
  initializer "action_mailer.compile_config_methods" do
@@ -79,18 +79,13 @@ module ActionMailer
79
79
  end
80
80
  end
81
81
 
82
- initializer "action_mailer.eager_load_actions" do
83
- ActiveSupport.on_load(:after_initialize) do
84
- ActionMailer::Base.descendants.each(&:action_methods) if config.eager_load
85
- end
86
- end
87
-
88
82
  config.after_initialize do |app|
89
83
  options = app.config.action_mailer
90
84
 
91
85
  if options.show_previews
92
86
  app.routes.prepend do
93
- get "/rails/mailers" => "rails/mailers#index", internal: true
87
+ get "/rails/mailers" => "rails/mailers#index", internal: true
88
+ get "/rails/mailers/download/*path" => "rails/mailers#download", internal: true
94
89
  get "/rails/mailers/*path" => "rails/mailers#preview", internal: true
95
90
  end
96
91
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionMailer # :nodoc:
4
+ # = Action Mailer \Rescuable
5
+ #
4
6
  # Provides
5
7
  # {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from]
6
8
  # for mailers. Wraps mailer action processing, mail job processing, and mail
@@ -74,6 +74,15 @@ module ActionMailer
74
74
  end
75
75
  end
76
76
 
77
+ # Reads the fixture file for the given mailer.
78
+ #
79
+ # This is useful when testing mailers by being able to write the body of
80
+ # an email inside a fixture. See the testing guide for a concrete example:
81
+ # https://guides.rubyonrails.org/testing.html#revenge-of-the-fixtures
82
+ def read_fixture(action)
83
+ IO.readlines(File.join(Rails.root, "test", "fixtures", self.class.mailer_class.name.underscore, action))
84
+ end
85
+
77
86
  private
78
87
  def initialize_test_deliveries
79
88
  set_delivery_method :test
@@ -110,10 +119,6 @@ module ActionMailer
110
119
  def encode(subject)
111
120
  Mail::Encodings.q_value_encode(subject, charset)
112
121
  end
113
-
114
- def read_fixture(action)
115
- IO.readlines(File.join(Rails.root, "test", "fixtures", self.class.mailer_class.name.underscore, action))
116
- end
117
122
  end
118
123
 
119
124
  include Behavior
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/array/extract_options"
3
4
  require "active_job"
4
5
 
5
6
  module ActionMailer
@@ -33,10 +34,8 @@ module ActionMailer
33
34
  # end
34
35
  def assert_emails(number, &block)
35
36
  if block_given?
36
- original_count = ActionMailer::Base.deliveries.size
37
- perform_enqueued_jobs(only: ->(job) { delivery_job_filter(job) }, &block)
38
- new_count = ActionMailer::Base.deliveries.size
39
- assert_equal number, new_count - original_count, "#{number} emails expected, but #{new_count - original_count} were sent"
37
+ diff = capture_emails(&block).length
38
+ assert_equal number, diff, "#{number} emails expected, but #{diff} were sent"
40
39
  else
41
40
  assert_equal number, ActionMailer::Base.deliveries.size
42
41
  end
@@ -94,18 +93,50 @@ module ActionMailer
94
93
  end
95
94
 
96
95
  # Asserts that a specific email has been enqueued, optionally
97
- # matching arguments.
96
+ # matching arguments and/or params.
98
97
  #
99
98
  # def test_email
100
99
  # ContactMailer.welcome.deliver_later
101
100
  # assert_enqueued_email_with ContactMailer, :welcome
102
101
  # end
103
102
  #
103
+ # def test_email_with_parameters
104
+ # ContactMailer.with(greeting: "Hello").welcome.deliver_later
105
+ # assert_enqueued_email_with ContactMailer, :welcome, args: { greeting: "Hello" }
106
+ # end
107
+ #
104
108
  # def test_email_with_arguments
105
109
  # ContactMailer.welcome("Hello", "Goodbye").deliver_later
106
110
  # assert_enqueued_email_with ContactMailer, :welcome, args: ["Hello", "Goodbye"]
107
111
  # end
108
112
  #
113
+ # def test_email_with_named_arguments
114
+ # ContactMailer.welcome(greeting: "Hello", farewell: "Goodbye").deliver_later
115
+ # assert_enqueued_email_with ContactMailer, :welcome, args: [{ greeting: "Hello", farewell: "Goodbye" }]
116
+ # end
117
+ #
118
+ # def test_email_with_parameters_and_arguments
119
+ # ContactMailer.with(greeting: "Hello").welcome("Cheers", "Goodbye").deliver_later
120
+ # assert_enqueued_email_with ContactMailer, :welcome, params: { greeting: "Hello" }, args: ["Cheers", "Goodbye"]
121
+ # end
122
+ #
123
+ # def test_email_with_parameters_and_named_arguments
124
+ # ContactMailer.with(greeting: "Hello").welcome(farewell: "Goodbye").deliver_later
125
+ # assert_enqueued_email_with ContactMailer, :welcome, params: { greeting: "Hello" }, args: [{farewell: "Goodbye"}]
126
+ # end
127
+ #
128
+ # def test_email_with_parameterized_mailer
129
+ # ContactMailer.with(greeting: "Hello").welcome.deliver_later
130
+ # assert_enqueued_email_with ContactMailer.with(greeting: "Hello"), :welcome
131
+ # end
132
+ #
133
+ # def test_email_with_matchers
134
+ # ContactMailer.with(greeting: "Hello").welcome("Cheers", "Goodbye").deliver_later
135
+ # assert_enqueued_email_with ContactMailer, :welcome,
136
+ # params: ->(params) { /hello/i.match?(params[:greeting]) },
137
+ # args: ->(args) { /cheers/i.match?(args[0]) }
138
+ # end
139
+ #
109
140
  # If a block is passed, that block should cause the specified email
110
141
  # to be enqueued.
111
142
  #
@@ -123,13 +154,43 @@ module ActionMailer
123
154
  # ContactMailer.with(email: 'user@example.com').welcome.deliver_later
124
155
  # end
125
156
  # end
126
- def assert_enqueued_email_with(mailer, method, args: nil, queue: ActionMailer::Base.deliver_later_queue_name || "default", &block)
127
- args = if args.is_a?(Hash)
128
- [mailer.to_s, method.to_s, "deliver_now", params: args, args: []]
129
- else
130
- [mailer.to_s, method.to_s, "deliver_now", args: Array(args)]
157
+ def assert_enqueued_email_with(mailer, method, params: nil, args: nil, queue: nil, &block)
158
+ if mailer.is_a? ActionMailer::Parameterized::Mailer
159
+ params = mailer.instance_variable_get(:@params)
160
+ mailer = mailer.instance_variable_get(:@mailer)
131
161
  end
132
- assert_enqueued_with(job: mailer.delivery_job, args: args, queue: queue.to_s, &block)
162
+
163
+ if args.is_a?(Hash)
164
+ ActionMailer.deprecator.warn <<~MSG
165
+ Passing a Hash to the assert_enqueued_email_with :args kwarg causes the
166
+ Hash to be treated as params. This behavior is deprecated and will be
167
+ removed in Rails 7.2.
168
+
169
+ To specify a params Hash, use the :params kwarg:
170
+
171
+ assert_enqueued_email_with MyMailer, :my_method, params: { my_param: "value" }
172
+
173
+ Or, to specify named mailer args as a Hash, wrap the Hash in an array:
174
+
175
+ assert_enqueued_email_with MyMailer, :my_method, args: [{ my_arg: "value" }]
176
+ # OR
177
+ assert_enqueued_email_with MyMailer, :my_method, args: [my_arg: "value"]
178
+ MSG
179
+
180
+ params, args = args, nil
181
+ end
182
+
183
+ args = Array(args) unless args.is_a?(Proc)
184
+ queue ||= mailer.deliver_later_queue_name || ActiveJob::Base.default_queue_name
185
+
186
+ expected = ->(job_args) do
187
+ job_kwargs = job_args.extract_options!
188
+
189
+ [mailer.to_s, method.to_s, "deliver_now"] == job_args &&
190
+ params === job_kwargs[:params] && args === job_kwargs[:args]
191
+ end
192
+
193
+ assert_enqueued_with(job: mailer.delivery_job, args: expected, queue: queue.to_s, &block)
133
194
  end
134
195
 
135
196
  # Asserts that no emails are enqueued for later delivery.
@@ -151,6 +212,68 @@ module ActionMailer
151
212
  assert_enqueued_emails 0, &block
152
213
  end
153
214
 
215
+ # Delivers all enqueued emails. If a block is given, delivers all of the emails
216
+ # that were enqueued throughout the duration of the block. If a block is
217
+ # not given, delivers all the enqueued emails up to this point in the test.
218
+ #
219
+ # def test_deliver_enqueued_emails
220
+ # deliver_enqueued_emails do
221
+ # ContactMailer.welcome.deliver_later
222
+ # end
223
+ #
224
+ # assert_emails 1
225
+ # end
226
+ #
227
+ # def test_deliver_enqueued_emails_without_block
228
+ # ContactMailer.welcome.deliver_later
229
+ #
230
+ # deliver_enqueued_emails
231
+ #
232
+ # assert_emails 1
233
+ # end
234
+ #
235
+ # If the +:queue+ option is specified,
236
+ # then only the emails(s) enqueued to a specific queue will be performed.
237
+ #
238
+ # def test_deliver_enqueued_emails_with_queue
239
+ # deliver_enqueued_emails queue: :external_mailers do
240
+ # CustomerMailer.deliver_later_queue_name = :external_mailers
241
+ # CustomerMailer.welcome.deliver_later # will be performed
242
+ # EmployeeMailer.deliver_later_queue_name = :internal_mailers
243
+ # EmployeeMailer.welcome.deliver_later # will not be performed
244
+ # end
245
+ #
246
+ # assert_emails 1
247
+ # end
248
+ #
249
+ # If the +:at+ option is specified, then only delivers emails enqueued to deliver
250
+ # immediately or before the given time.
251
+ def deliver_enqueued_emails(queue: nil, at: nil, &block)
252
+ perform_enqueued_jobs(only: ->(job) { delivery_job_filter(job) }, queue: queue, at: at, &block)
253
+ end
254
+
255
+ # Returns any emails that are sent in the block.
256
+ #
257
+ # def test_emails
258
+ # emails = capture_emails do
259
+ # ContactMailer.welcome.deliver_now
260
+ # end
261
+ # assert_equal "Hi there", emails.first.subject
262
+ #
263
+ # emails = capture_emails do
264
+ # ContactMailer.welcome.deliver_now
265
+ # ContactMailer.welcome.deliver_later
266
+ # end
267
+ # assert_equal "Hi there", emails.first.subject
268
+ # end
269
+ def capture_emails(&block)
270
+ original_count = ActionMailer::Base.deliveries.size
271
+ deliver_enqueued_emails(&block)
272
+ new_count = ActionMailer::Base.deliveries.size
273
+ diff = new_count - original_count
274
+ ActionMailer::Base.deliveries.last(diff)
275
+ end
276
+
154
277
  private
155
278
  def delivery_job_filter(job)
156
279
  job_class = job.is_a?(Hash) ? job.fetch(:job) : job.class
@@ -4,7 +4,7 @@ require_relative "gem_version"
4
4
 
5
5
  module ActionMailer
6
6
  # Returns the currently loaded version of Action Mailer as a
7
- # <tt>Gem::Version</tt>.
7
+ # +Gem::Version+.
8
8
  def self.version
9
9
  gem_version
10
10
  end