aboutsummaryrefslogtreecommitdiffstats
path: root/actionmailer
diff options
context:
space:
mode:
Diffstat (limited to 'actionmailer')
-rw-r--r--actionmailer/CHANGELOG.md110
-rw-r--r--actionmailer/MIT-LICENSE2
-rw-r--r--actionmailer/README.rdoc6
-rw-r--r--actionmailer/Rakefile12
-rw-r--r--actionmailer/actionmailer.gemspec44
-rwxr-xr-xactionmailer/bin/test7
-rw-r--r--actionmailer/lib/action_mailer.rb31
-rw-r--r--actionmailer/lib/action_mailer/base.rb389
-rw-r--r--actionmailer/lib/action_mailer/collector.rb8
-rw-r--r--actionmailer/lib/action_mailer/delivery_job.rb25
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb35
-rw-r--r--actionmailer/lib/action_mailer/gem_version.rb4
-rw-r--r--actionmailer/lib/action_mailer/inline_preview_interceptor.rb22
-rw-r--r--actionmailer/lib/action_mailer/log_subscriber.rb13
-rw-r--r--actionmailer/lib/action_mailer/mail_helper.rb4
-rw-r--r--actionmailer/lib/action_mailer/message_delivery.rb90
-rw-r--r--actionmailer/lib/action_mailer/parameterized.rb155
-rw-r--r--actionmailer/lib/action_mailer/preview.rb71
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb42
-rw-r--r--actionmailer/lib/action_mailer/rescuable.rb29
-rw-r--r--actionmailer/lib/action_mailer/test_case.rb51
-rw-r--r--actionmailer/lib/action_mailer/test_helper.rb58
-rw-r--r--actionmailer/lib/action_mailer/version.rb4
-rw-r--r--actionmailer/lib/rails/generators/mailer/mailer_generator.rb27
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb4
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb.tt6
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/mailer.rb.tt (renamed from actionmailer/lib/rails/generators/mailer/templates/mailer.rb)0
-rw-r--r--actionmailer/test/abstract_unit.rb43
-rw-r--r--actionmailer/test/assert_select_email_test.rb12
-rw-r--r--actionmailer/test/asset_host_test.rb20
-rw-r--r--actionmailer/test/base_test.rb381
-rw-r--r--actionmailer/test/caching_test.rb271
-rw-r--r--actionmailer/test/delivery_methods_test.rb28
-rw-r--r--actionmailer/test/fixtures/async_mailer/welcome.erb1
-rw-r--r--actionmailer/test/fixtures/caching_mailer/_partial.html.erb3
-rw-r--r--actionmailer/test/fixtures/caching_mailer/fragment_cache.html.erb3
-rw-r--r--actionmailer/test/fixtures/caching_mailer/fragment_cache_in_partials.html.erb1
-rw-r--r--actionmailer/test/fixtures/caching_mailer/fragment_caching_options.html.erb3
-rw-r--r--actionmailer/test/fixtures/caching_mailer/multipart_cache.html.erb3
-rw-r--r--actionmailer/test/fixtures/caching_mailer/multipart_cache.text.erb3
-rw-r--r--actionmailer/test/fixtures/caching_mailer/skip_fragment_cache_digesting.html.erb3
-rw-r--r--actionmailer/test/i18n_with_controller_test.rb36
-rw-r--r--actionmailer/test/log_subscriber_test.rb28
-rw-r--r--actionmailer/test/mail_helper_test.rb24
-rw-r--r--actionmailer/test/mail_layout_test.rb20
-rw-r--r--actionmailer/test/mailers/asset_mailer.rb4
-rw-r--r--actionmailer/test/mailers/base_mailer.rb49
-rw-r--r--actionmailer/test/mailers/caching_mailer.rb25
-rw-r--r--actionmailer/test/mailers/delayed_mailer.rb24
-rw-r--r--actionmailer/test/mailers/params_mailer.rb13
-rw-r--r--actionmailer/test/mailers/proc_mailer.rb22
-rw-r--r--actionmailer/test/message_delivery_test.rb127
-rw-r--r--actionmailer/test/parameterized_test.rb69
-rw-r--r--actionmailer/test/test_case_test.rb44
-rw-r--r--actionmailer/test/test_helper_test.rb121
-rw-r--r--actionmailer/test/url_test.rb66
56 files changed, 1967 insertions, 729 deletions
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index b19d6b87c0..35574a2c73 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -3,95 +3,65 @@
*Marcus Ilgner*
-* `config.force_ssl = true` will set
- `config.action_mailer.default_url_options = { protocol: 'https' }`
-
- *Andrew Kampjes*
-
-* Add `config.action_mailer.deliver_later_queue_name` configuration to set the
- mailer queue name.
-
- *Chris McGrath*
-
-* `assert_emails` in block form use the given number as expected value.
- This makes the error message much easier to understand.
-
- *Yuji Yaginuma*
-
-* Add support for inline images in mailer previews by using an interceptor
- class to convert cid: urls in image src attributes to data urls.
-
- *Andrew White*
+* Allow ActionMailer classes to configure the parameterized delivery job
+ Example:
+ ```
+ class MyMailer < ApplicationMailer
+ self.parameterized_delivery_job = MyCustomDeliveryJob
+ ...
+ end
+ ```
-* Mailer preview now uses `url_for` to fix links to emails for apps running on
- a subdirectory.
+ *Luke Pearce*
- *Remo Mueller*
+* `ActionDispatch::IntegrationTest` includes `ActionMailer::TestHelper` module by default.
-* Mailer previews no longer crash when the `mail` method wasn't called
- (`NullMail`).
+ *Ricardo Díaz*
- Fixes #19849.
+* Add `perform_deliveries` to a payload of `deliver.action_mailer` notification.
- *Yves Senn*
+ *Yoshiyuki Kinjo*
-* Make sure labels and values line up in mailer previews.
+* Change delivery logging message when `perform_deliveries` is false.
- *Yves Senn*
+ *Yoshiyuki Kinjo*
-* Add `assert_enqueued_emails` and `assert_no_enqueued_emails`.
+* Allow call `assert_enqueued_email_with` with no block.
Example:
+ ```
+ def test_email
+ ContactMailer.welcome.deliver_later
+ assert_enqueued_email_with ContactMailer, :welcome
+ end
- def test_emails
- assert_enqueued_emails 2 do
- ContactMailer.welcome.deliver_later
- ContactMailer.welcome.deliver_later
- end
- end
-
- def test_no_emails
- assert_no_enqueued_emails do
- # No emails enqueued here
- end
- end
-
- *George Claghorn*
-
-* Add `_mailer` suffix to mailers created via generator, following the same
- naming convention used in controllers and jobs.
-
- *Carlos Souza*
-
-* Remove deprecate `*_path` helpers in email views.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `deliver` and `deliver!` methods.
+ def test_email_with_arguments
+ ContactMailer.welcome("Hello", "Goodbye").deliver_later
+ assert_enqueued_email_with ContactMailer, :welcome, args: ["Hello", "Goodbye"]
+ end
+ ```
- *claudiob*
+ *bogdanvlviv*
-* Template lookup now respects default locale and I18n fallbacks.
+* Ensure mail gem is eager autoloaded when eager load is true to prevent thread deadlocks.
- Given the following templates:
+ *Samuel Cochran*
- mailer/demo.html.erb
- mailer/demo.en.html.erb
- mailer/demo.pt.html.erb
+* Perform email jobs in `assert_emails`.
- Before this change, for a locale that doesn't have its associated file, the
- `mailer/demo.html.erb` would be rendered even if `en` was the default locale.
+ *Gannon McGibbon*
- Now `mailer/demo.en.html.erb` has precedence over the file without locale.
+* Add `Base.unregister_observer`, `Base.unregister_observers`,
+ `Base.unregister_interceptor`, `Base.unregister_interceptors`,
+ `Base.unregister_preview_interceptor` and `Base.unregister_preview_interceptors`.
+ This makes it possible to dynamically add and remove email observers and
+ interceptors at runtime in the same way they're registered.
- Also, it is possible to give a fallback.
+ *Claudio Ortolina*, *Kota Miyake*
- mailer/demo.pt.html.erb
- mailer/demo.pt-BR.html.erb
+* Rails 6 requires Ruby 2.4.1 or newer.
- So if the locale is `pt-PT`, `mailer/demo.pt.html.erb` will be rendered given
- the right I18n fallback configuration.
+ *Jeremy Daer*
- *Rafael Mendonça França*
-Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionmailer/CHANGELOG.md) for previous changes.
+Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/actionmailer/CHANGELOG.md) for previous changes.
diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE
index 3ec7a617cf..1cb3add0fc 100644
--- a/actionmailer/MIT-LICENSE
+++ b/actionmailer/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2015 David Heinemeier Hansson
+Copyright (c) 2004-2018 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index 397ebe4201..14dfb82234 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -148,7 +148,7 @@ The latest version of Action Mailer can be installed with RubyGems:
$ gem install actionmailer
-Source code can be downloaded as part of the Rails project on GitHub
+Source code can be downloaded as part of the Rails project on GitHub:
* https://github.com/rails/rails/tree/master/actionmailer
@@ -157,7 +157,7 @@ Source code can be downloaded as part of the Rails project on GitHub
Action Mailer is released under the MIT license:
-* http://www.opensource.org/licenses/MIT
+* https://opensource.org/licenses/MIT
== Support
@@ -166,7 +166,7 @@ API documentation is at
* http://api.rubyonrails.org
-Bug reports can be filed for the Ruby on Rails project here:
+Bug reports for the Ruby on Rails project can be filed here:
* https://github.com/rails/rails/issues
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index 7197ea5e27..6ac408e1cb 100644
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -1,12 +1,16 @@
-require 'rake/testtask'
+# frozen_string_literal: true
+
+require "rake/testtask"
desc "Default Task"
task default: [ :test ]
+task :package
+
# Run the unit tests
Rake::TestTask.new { |t|
t.libs << "test"
- t.pattern = 'test/**/*_test.rb'
+ t.pattern = "test/**/*_test.rb"
t.warning = true
t.verbose = true
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
@@ -15,7 +19,7 @@ Rake::TestTask.new { |t|
namespace :test do
task :isolated do
Dir.glob("test/**/*_test.rb").all? do |file|
- sh(Gem.ruby, '-w', '-Ilib:test', file)
- end or raise "Failures"
+ sh(Gem.ruby, "-w", "-Ilib:test", file)
+ end || raise("Failures")
end
end
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index 782b208ef4..6f8d6d0699 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -1,28 +1,38 @@
-version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip
+# frozen_string_literal: true
+
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
- s.name = 'actionmailer'
+ s.name = "actionmailer"
s.version = version
- s.summary = 'Email composition, delivery, and receiving framework (part of Rails).'
- s.description = 'Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments.'
+ s.summary = "Email composition, delivery, and receiving framework (part of Rails)."
+ s.description = "Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments."
+
+ s.required_ruby_version = ">= 2.4.1"
+
+ s.license = "MIT"
- s.required_ruby_version = '>= 2.2.2'
+ s.author = "David Heinemeier Hansson"
+ s.email = "david@loudthinking.com"
+ s.homepage = "http://rubyonrails.org"
- s.license = 'MIT'
+ s.files = Dir["CHANGELOG.md", "README.rdoc", "MIT-LICENSE", "lib/**/*"]
+ s.require_path = "lib"
+ s.requirements << "none"
- s.author = 'David Heinemeier Hansson'
- s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.metadata = {
+ "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/actionmailer",
+ "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/actionmailer/CHANGELOG.md"
+ }
- s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*']
- s.require_path = 'lib'
- s.requirements << 'none'
+ # NOTE: Please read our dependency guidelines before updating versions:
+ # https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves
- s.add_dependency 'actionpack', version
- s.add_dependency 'actionview', version
- s.add_dependency 'activejob', version
+ s.add_dependency "actionpack", version
+ s.add_dependency "actionview", version
+ s.add_dependency "activejob", version
- s.add_dependency 'mail', ['~> 2.5', '>= 2.5.4']
- s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5'
+ s.add_dependency "mail", ["~> 2.5", ">= 2.5.4"]
+ s.add_dependency "rails-dom-testing", "~> 2.0"
end
diff --git a/actionmailer/bin/test b/actionmailer/bin/test
index 404cabba51..c53377cc97 100755
--- a/actionmailer/bin/test
+++ b/actionmailer/bin/test
@@ -1,4 +1,5 @@
#!/usr/bin/env ruby
-COMPONENT_ROOT = File.expand_path("../../", __FILE__)
-require File.expand_path("../tools/test", COMPONENT_ROOT)
-exit Minitest.run(ARGV)
+# frozen_string_literal: true
+
+COMPONENT_ROOT = File.expand_path("..", __dir__)
+require_relative "../../tools/test"
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 312dd1997c..69eae65d60 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
#--
-# Copyright (c) 2004-2015 David Heinemeier Hansson
+# Copyright (c) 2004-2018 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -21,15 +23,16 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-require 'abstract_controller'
-require 'action_mailer/version'
+require "abstract_controller"
+require "action_mailer/version"
# Common Active Support usage in Action Mailer
-require 'active_support/rails'
-require 'active_support/core_ext/class'
-require 'active_support/core_ext/module/attr_internal'
-require 'active_support/core_ext/string/inflections'
-require 'active_support/lazy_load_hooks'
+require "active_support"
+require "active_support/rails"
+require "active_support/core_ext/class"
+require "active_support/core_ext/module/attr_internal"
+require "active_support/core_ext/string/inflections"
+require "active_support/lazy_load_hooks"
module ActionMailer
extend ::ActiveSupport::Autoload
@@ -42,15 +45,23 @@ module ActionMailer
autoload :DeliveryMethods
autoload :InlinePreviewInterceptor
autoload :MailHelper
+ autoload :Parameterized
autoload :Preview
- autoload :Previews, 'action_mailer/preview'
+ autoload :Previews, "action_mailer/preview"
autoload :TestCase
autoload :TestHelper
autoload :MessageDelivery
autoload :DeliveryJob
+
+ def self.eager_load!
+ super
+
+ require "mail"
+ Mail.eager_autoload!
+ end
end
-autoload :Mime, 'action_dispatch/http/mime_type'
+autoload :Mime, "action_dispatch/http/mime_type"
ActiveSupport.on_load(:action_view) do
ActionView::Base.default_formats ||= Mime::SET.symbols
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 9dd3f151ad..2bb1078bb8 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -1,10 +1,13 @@
-require 'mail'
-require 'action_mailer/collector'
-require 'active_support/core_ext/string/inflections'
-require 'active_support/core_ext/hash/except'
-require 'active_support/core_ext/module/anonymous'
+# frozen_string_literal: true
-require 'action_mailer/log_subscriber'
+require "mail"
+require "action_mailer/collector"
+require "active_support/core_ext/string/inflections"
+require "active_support/core_ext/hash/except"
+require "active_support/core_ext/module/anonymous"
+
+require "action_mailer/log_subscriber"
+require "action_mailer/rescuable"
module ActionMailer
# Action Mailer allows you to send email from your application using a mailer model and views.
@@ -56,7 +59,7 @@ module ActionMailer
# The hash passed to the mail method allows you to specify any header that a <tt>Mail::Message</tt>
# will accept (any valid email header including optional fields).
#
- # The mail method, if not passed a block, will inspect your views and send all the views with
+ # The +mail+ method, if not passed a block, will inspect your views and send all the views with
# the same name as the method, so the above action would send the +welcome.text.erb+ view
# file as well as the +welcome.html.erb+ view file in a +multipart/alternative+ email.
#
@@ -86,7 +89,7 @@ module ActionMailer
# Like Action Controller, each mailer class has a corresponding view directory in which each
# method of the class looks for a template with its name.
#
- # To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same
+ # To define a template to be used with a mailer, create an <tt>.erb</tt> file with the same
# name as the method in your mailer model. For example, in the mailer defined above, the template at
# <tt>app/views/notifier_mailer/welcome.text.erb</tt> would be used to generate the email.
#
@@ -132,7 +135,10 @@ module ActionMailer
#
# config.action_mailer.default_url_options = { host: "example.com" }
#
- # By default when <tt>config.force_ssl</tt> is true, URLs generated for hosts will use the HTTPS protocol.
+ # You can also define a <tt>default_url_options</tt> method on individual mailers to override these
+ # default settings per-mailer.
+ #
+ # By default when <tt>config.force_ssl</tt> is +true+, URLs generated for hosts will use the HTTPS protocol.
#
# = Sending mail
#
@@ -144,7 +150,7 @@ module ActionMailer
# mail.deliver_now # generates and sends the email now
#
# The <tt>ActionMailer::MessageDelivery</tt> class is a wrapper around a delegate that will call
- # your method to generate the mail. If you want direct access to delegator, or <tt>Mail::Message</tt>,
+ # your method to generate the mail. If you want direct access to the delegator, or <tt>Mail::Message</tt>,
# you can call the <tt>message</tt> method on the <tt>ActionMailer::MessageDelivery</tt> object.
#
# NotifierMailer.welcome(User.first).message # => a Mail::Message object
@@ -163,7 +169,7 @@ module ActionMailer
#
# Multipart messages can also be used implicitly because Action Mailer will automatically detect and use
# multipart templates, where each template is named after the name of the action, followed by the content
- # type. Each such detected template will be added as a separate part to the message.
+ # type. Each such detected template will be added to the message, as a separate part.
#
# For example, if the following templates exist:
# * signup_notification.text.erb
@@ -207,6 +213,19 @@ module ActionMailer
# end
# end
#
+ # You can also send attachments with html template, in this case you need to add body, attachments,
+ # and custom content type like this:
+ #
+ # class NotifierMailer < ApplicationMailer
+ # def welcome(recipient)
+ # attachments["free_book.pdf"] = File.read("path/to/file.pdf")
+ # mail(to: recipient,
+ # subject: "New account information",
+ # content_type: "text/html",
+ # body: "<html><body>Hello there</body></html>")
+ # end
+ # end
+ #
# = Inline Attachments
#
# You can also specify that a file should be displayed inline with other HTML. This is useful
@@ -274,22 +293,21 @@ module ActionMailer
# content_description: 'This is a description'
# end
#
- # Finally, Action Mailer also supports passing <tt>Proc</tt> objects into the default hash, so you
- # can define methods that evaluate as the message is being generated:
+ # Finally, Action Mailer also supports passing <tt>Proc</tt> and <tt>Lambda</tt> objects into the default hash,
+ # so you can define methods that evaluate as the message is being generated:
#
# class NotifierMailer < ApplicationMailer
- # default 'X-Special-Header' => Proc.new { my_method }
+ # default 'X-Special-Header' => Proc.new { my_method }, to: -> { @inviter.email_address }
#
# private
- #
# def my_method
# 'some complex call'
# end
# end
#
- # Note that the proc is evaluated right at the start of the mail message generation, so if you
- # set something in the default using a proc, and then set the same thing inside of your
- # mailer method, it will get over written by the mailer method.
+ # Note that the proc/lambda is evaluated right at the start of the mail message generation, so if you
+ # set something in the default hash using a proc, and then set the same thing inside of your
+ # mailer method, it will get overwritten by the mailer method.
#
# It is also possible to set these default options that will be used in all mailers through
# the <tt>default_options=</tt> configuration in <tt>config/application.rb</tt>:
@@ -298,7 +316,7 @@ module ActionMailer
#
# = Callbacks
#
- # You can specify callbacks using before_action and after_action for configuring your messages.
+ # You can specify callbacks using <tt>before_action</tt> and <tt>after_action</tt> for configuring your messages.
# This may be useful, for example, when you want to add default inline attachments for all
# messages sent out by a certain mailer class:
#
@@ -310,7 +328,6 @@ module ActionMailer
# end
#
# private
- #
# def add_inline_attachment!
# attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg')
# end
@@ -389,13 +406,13 @@ module ActionMailer
# to use it. Defaults to <tt>true</tt>.
# * <tt>:openssl_verify_mode</tt> - When using TLS, you can set how OpenSSL checks the certificate. This is
# really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name
- # of an OpenSSL verify constant (<tt>'none'</tt>, <tt>'peer'</tt>, <tt>'client_once'</tt>,
- # <tt>'fail_if_no_peer_cert'</tt>) or directly the constant (<tt>OpenSSL::SSL::VERIFY_NONE</tt>,
- # <tt>OpenSSL::SSL::VERIFY_PEER</tt>, ...).
+ # of an OpenSSL verify constant (<tt>'none'</tt> or <tt>'peer'</tt>) or directly the constant
+ # (<tt>OpenSSL::SSL::VERIFY_NONE</tt> or <tt>OpenSSL::SSL::VERIFY_PEER</tt>).
+ # <tt>:ssl/:tls</tt> Enables the SMTP connection to use SMTP/TLS (SMTPS: SMTP over direct TLS connection)
#
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
# * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
- # * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i -t</tt> with <tt>-f sender@address</tt>
+ # * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i</tt> with <tt>-f sender@address</tt>
# added automatically before the message is sent.
#
# * <tt>file_settings</tt> - Allows you to override options for the <tt>:file</tt> delivery method.
@@ -416,9 +433,11 @@ module ActionMailer
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with
# <tt>delivery_method :test</tt>. Most useful for unit and functional testing.
#
- # * <tt>deliver_later_queue_name</tt> - The name of the queue used with <tt>deliver_later</tt>.
+ # * <tt>deliver_later_queue_name</tt> - The name of the queue used with <tt>deliver_later</tt>. Defaults to +mailers+.
class Base < AbstractController::Base
include DeliveryMethods
+ include Rescuable
+ include Parameterized
include Previews
abstract!
@@ -430,6 +449,7 @@ module ActionMailer
include AbstractController::Translation
include AbstractController::AssetPaths
include AbstractController::Callbacks
+ include AbstractController::Caching
include ActionView::Layouts
@@ -441,8 +461,9 @@ module ActionMailer
helper ActionMailer::MailHelper
- class_attribute :default_params
- self.default_params = {
+ class_attribute :delivery_job, default: ::ActionMailer::DeliveryJob
+ class_attribute :parameterized_delivery_job, default: ::ActionMailer::Parameterized::DeliveryJob
+ class_attribute :default_params, default: {
mime_version: "1.0",
charset: "UTF-8",
content_type: "text/plain",
@@ -455,11 +476,21 @@ module ActionMailer
observers.flatten.compact.each { |observer| register_observer(observer) }
end
+ # Unregister one or more previously registered Observers.
+ def unregister_observers(*observers)
+ observers.flatten.compact.each { |observer| unregister_observer(observer) }
+ end
+
# Register one or more Interceptors which will be called before mail is sent.
def register_interceptors(*interceptors)
interceptors.flatten.compact.each { |interceptor| register_interceptor(interceptor) }
end
+ # Unregister one or more previously registered Interceptors.
+ def unregister_interceptors(*interceptors)
+ interceptors.flatten.compact.each { |interceptor| unregister_interceptor(interceptor) }
+ end
+
# Register an Observer which will be notified when mail is delivered.
# Either a class, string or symbol can be passed in as the Observer.
# If a string or symbol is passed in it will be camelized and constantized.
@@ -467,6 +498,13 @@ module ActionMailer
Mail.register_observer(observer_class_for(observer))
end
+ # Unregister a previously registered Observer.
+ # Either a class, string or symbol can be passed in as the Observer.
+ # If a string or symbol is passed in it will be camelized and constantized.
+ def unregister_observer(observer)
+ Mail.unregister_observer(observer_class_for(observer))
+ end
+
# Register an Interceptor which will be called before mail is sent.
# Either a class, string or symbol can be passed in as the Interceptor.
# If a string or symbol is passed in it will be camelized and constantized.
@@ -474,6 +512,13 @@ module ActionMailer
Mail.register_interceptor(observer_class_for(interceptor))
end
+ # Unregister a previously registered Interceptor.
+ # Either a class, string or symbol can be passed in as the Interceptor.
+ # If a string or symbol is passed in it will be camelized and constantized.
+ def unregister_interceptor(interceptor)
+ Mail.unregister_interceptor(observer_class_for(interceptor))
+ end
+
def observer_class_for(value) # :nodoc:
case value
when String, Symbol
@@ -484,7 +529,7 @@ module ActionMailer
end
private :observer_class_for
- # Returns the name of current mailer. This method is also being used as a path for a view lookup.
+ # Returns the name of the current mailer. This method is also being used as a path for a view lookup.
# If this is an anonymous mailer, this method will return +anonymous+ instead.
def mailer_name
@mailer_name ||= anonymous? ? "anonymous" : name.underscore
@@ -541,21 +586,22 @@ module ActionMailer
end
end
- protected
+ private
- def set_payload_for_mail(payload, mail) #:nodoc:
- payload[:mailer] = name
- payload[:message_id] = mail.message_id
- payload[:subject] = mail.subject
- payload[:to] = mail.to
- payload[:from] = mail.from
- payload[:bcc] = mail.bcc if mail.bcc.present?
- payload[:cc] = mail.cc if mail.cc.present?
- payload[:date] = mail.date
- payload[:mail] = mail.encoded
+ def set_payload_for_mail(payload, mail)
+ payload[:mailer] = name
+ payload[:message_id] = mail.message_id
+ payload[:subject] = mail.subject
+ payload[:to] = mail.to
+ payload[:from] = mail.from
+ payload[:bcc] = mail.bcc if mail.bcc.present?
+ payload[:cc] = mail.cc if mail.cc.present?
+ payload[:date] = mail.date
+ payload[:mail] = mail.encoded
+ payload[:perform_deliveries] = mail.perform_deliveries
end
- def method_missing(method_name, *args) # :nodoc:
+ def method_missing(method_name, *args)
if action_methods.include?(method_name.to_s)
MessageDelivery.new(self, method_name, *args)
else
@@ -563,19 +609,13 @@ module ActionMailer
end
end
- private
-
- def respond_to_missing?(method, include_all = false) #:nodoc:
- action_methods.include?(method.to_s)
+ def respond_to_missing?(method, include_all = false)
+ action_methods.include?(method.to_s) || super
end
end
attr_internal :message
- # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
- # will be initialized according to the named method. If not, the mailer will
- # remain uninitialized (useful when you only need to invoke the "receive"
- # method, for instance).
def initialize
super()
@_mail_was_called = false
@@ -585,7 +625,8 @@ module ActionMailer
def process(method_name, *args) #:nodoc:
payload = {
mailer: self.class.name,
- action: method_name
+ action: method_name,
+ args: args
}
ActiveSupport::Notifications.instrument("process.action_mailer", payload) do
@@ -595,10 +636,10 @@ module ActionMailer
end
class NullMail #:nodoc:
- def body; '' end
+ def body; "" end
def header; {} end
- def respond_to?(string, include_all=false)
+ def respond_to?(string, include_all = false)
true
end
@@ -658,21 +699,21 @@ module ActionMailer
#
# mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
#
- # If you do this, then Mail will take the file name and work out the mime type
- # set the Content-Type, Content-Disposition, Content-Transfer-Encoding and
- # base64 encode the contents of the attachment all for you.
+ # If you do this, then Mail will take the file name and work out the mime type.
+ # It will also set the Content-Type, Content-Disposition, Content-Transfer-Encoding
+ # and encode the contents of the attachment in Base64.
#
# You can also specify overrides if you want by passing a hash instead of a string:
#
- # mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip',
+ # mail.attachments['filename.jpg'] = {mime_type: 'application/gzip',
# content: File.read('/path/to/filename.jpg')}
#
- # If you want to use a different encoding than Base64, you can pass an encoding in,
- # but then it is up to you to pass in the content pre-encoded, and don't expect
- # Mail to know how to decode this data:
+ # If you want to use encoding other than Base64 then you will need to pass encoding
+ # type along with the pre-encoded content as Mail doesn't know how to decode the
+ # data:
#
# file_content = SpecialEncode(File.read('/path/to/filename.jpg'))
- # mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip',
+ # mail.attachments['filename.jpg'] = {mime_type: 'application/gzip',
# encoding: 'SpecialEncoding',
# content: file_content }
#
@@ -827,131 +868,155 @@ module ActionMailer
message
end
- protected
-
- # Used by #mail to set the content type of the message.
- #
- # It will use the given +user_content_type+, or multipart if the mail
- # message has any attachments. If the attachments are inline, the content
- # type will be "multipart/related", otherwise "multipart/mixed".
- #
- # If there is no content type passed in via headers, and there are no
- # attachments, or the message is multipart, then the default content type is
- # used.
- def set_content_type(m, user_content_type, class_default)
- params = m.content_type_parameters || {}
- case
- when user_content_type.present?
- user_content_type
- when m.has_attachments?
- if m.attachments.detect(&:inline?)
- ["multipart", "related", params]
+ private
+
+ # Used by #mail to set the content type of the message.
+ #
+ # It will use the given +user_content_type+, or multipart if the mail
+ # message has any attachments. If the attachments are inline, the content
+ # type will be "multipart/related", otherwise "multipart/mixed".
+ #
+ # If there is no content type passed in via headers, and there are no
+ # attachments, or the message is multipart, then the default content type is
+ # used.
+ def set_content_type(m, user_content_type, class_default) # :doc:
+ params = m.content_type_parameters || {}
+ case
+ when user_content_type.present?
+ user_content_type
+ when m.has_attachments?
+ if m.attachments.detect(&:inline?)
+ ["multipart", "related", params]
+ else
+ ["multipart", "mixed", params]
+ end
+ when m.multipart?
+ ["multipart", "alternative", params]
else
- ["multipart", "mixed", params]
+ m.content_type || class_default
end
- when m.multipart?
- ["multipart", "alternative", params]
- else
- m.content_type || class_default
end
- end
- # Translates the +subject+ using Rails I18n class under <tt>[mailer_scope, action_name]</tt> scope.
- # If it does not find a translation for the +subject+ under the specified scope it will default to a
- # humanized version of the <tt>action_name</tt>.
- # If the subject has interpolations, you can pass them through the +interpolations+ parameter.
- def default_i18n_subject(interpolations = {})
- mailer_scope = self.class.mailer_name.tr('/', '.')
- I18n.t(:subject, interpolations.merge(scope: [mailer_scope, action_name], default: action_name.humanize))
- end
+ # Translates the +subject+ using Rails I18n class under <tt>[mailer_scope, action_name]</tt> scope.
+ # If it does not find a translation for the +subject+ under the specified scope it will default to a
+ # humanized version of the <tt>action_name</tt>.
+ # If the subject has interpolations, you can pass them through the +interpolations+ parameter.
+ def default_i18n_subject(interpolations = {}) # :doc:
+ mailer_scope = self.class.mailer_name.tr("/", ".")
+ I18n.t(:subject, interpolations.merge(scope: [mailer_scope, action_name], default: action_name.humanize))
+ end
- # Emails do not support relative path links.
- def self.supports_path?
- false
- end
+ # Emails do not support relative path links.
+ def self.supports_path? # :doc:
+ false
+ end
- private
+ def apply_defaults(headers)
+ default_values = self.class.default.map do |key, value|
+ [
+ key,
+ compute_default(value)
+ ]
+ end.to_h
+
+ headers_with_defaults = headers.reverse_merge(default_values)
+ headers_with_defaults[:subject] ||= default_i18n_subject
+ headers_with_defaults
+ end
- def apply_defaults(headers)
- default_values = self.class.default.map do |key, value|
- [
- key,
- value.is_a?(Proc) ? instance_eval(&value) : value
- ]
- end.to_h
-
- headers_with_defaults = headers.reverse_merge(default_values)
- headers_with_defaults[:subject] ||= default_i18n_subject
- headers_with_defaults
- end
+ def compute_default(value)
+ return value unless value.is_a?(Proc)
- def assign_headers_to_message(message, headers)
- assignable = headers.except(:parts_order, :content_type, :body, :template_name,
- :template_path, :delivery_method, :delivery_method_options)
- assignable.each { |k, v| message[k] = v }
- end
+ if value.arity == 1
+ instance_exec(self, &value)
+ else
+ instance_exec(&value)
+ end
+ end
+
+ def assign_headers_to_message(message, headers)
+ assignable = headers.except(:parts_order, :content_type, :body, :template_name,
+ :template_path, :delivery_method, :delivery_method_options)
+ assignable.each { |k, v| message[k] = v }
+ end
+
+ def collect_responses(headers)
+ if block_given?
+ collect_responses_from_block(headers, &Proc.new)
+ elsif headers[:body]
+ collect_responses_from_text(headers)
+ else
+ collect_responses_from_templates(headers)
+ end
+ end
+
+ def collect_responses_from_block(headers)
+ templates_name = headers[:template_name] || action_name
+ collector = ActionMailer::Collector.new(lookup_context) { render(templates_name) }
+ yield(collector)
+ collector.responses
+ end
- def collect_responses(headers)
- if block_given?
- collect_responses_from_block(headers, &Proc.new)
- elsif headers[:body]
+ def collect_responses_from_text(headers)
[{
body: headers.delete(:body),
- content_type: self.class.default[:content_type] || "text/plain"
+ content_type: headers[:content_type] || "text/plain"
}]
- else
- collect_responses_from_templates(headers)
end
- end
- def collect_responses_from_block(headers)
- templates_name = headers[:template_name] || action_name
- collector = ActionMailer::Collector.new(lookup_context) { render(templates_name) }
- yield(collector)
- collector.responses
- end
+ def collect_responses_from_templates(headers)
+ templates_path = headers[:template_path] || self.class.mailer_name
+ templates_name = headers[:template_name] || action_name
+
+ each_template(Array(templates_path), templates_name).map do |template|
+ self.formats = template.formats
+ {
+ body: render(template: template),
+ content_type: template.type.to_s
+ }
+ end
+ end
- def collect_responses_from_templates(headers)
- templates_path = headers[:template_path] || self.class.mailer_name
- templates_name = headers[:template_name] || action_name
+ def each_template(paths, name, &block)
+ templates = lookup_context.find_all(name, paths)
+ if templates.empty?
+ raise ActionView::MissingTemplate.new(paths, name, paths, false, "mailer")
+ else
+ templates.uniq(&:formats).each(&block)
+ end
+ end
- each_template(Array(templates_path), templates_name).map do |template|
- self.formats = template.formats
- {
- body: render(template: template),
- content_type: template.type.to_s
- }
+ def create_parts_from_responses(m, responses)
+ if responses.size == 1 && !m.has_attachments?
+ responses[0].each { |k, v| m[k] = v }
+ elsif responses.size > 1 && m.has_attachments?
+ container = Mail::Part.new
+ container.content_type = "multipart/alternative"
+ responses.each { |r| insert_part(container, r, m.charset) }
+ m.add_part(container)
+ else
+ responses.each { |r| insert_part(m, r, m.charset) }
+ end
end
- end
- def each_template(paths, name, &block)
- templates = lookup_context.find_all(name, paths)
- if templates.empty?
- raise ActionView::MissingTemplate.new(paths, name, paths, false, 'mailer')
- else
- templates.uniq(&:formats).each(&block)
+ def insert_part(container, response, charset)
+ response[:charset] ||= charset
+ part = Mail::Part.new(response)
+ container.add_part(part)
end
- end
- def create_parts_from_responses(m, responses)
- if responses.size == 1 && !m.has_attachments?
- responses[0].each { |k,v| m[k] = v }
- elsif responses.size > 1 && m.has_attachments?
- container = Mail::Part.new
- container.content_type = "multipart/alternative"
- responses.each { |r| insert_part(container, r, m.charset) }
- m.add_part(container)
- else
- responses.each { |r| insert_part(m, r, m.charset) }
+ # This and #instrument_name is for caching instrument
+ def instrument_payload(key)
+ {
+ mailer: mailer_name,
+ key: key
+ }
end
- end
- def insert_part(container, response, charset)
- response[:charset] ||= charset
- part = Mail::Part.new(response)
- container.add_part(part)
- end
+ def instrument_name
+ "action_mailer"
+ end
- ActiveSupport.run_load_hooks(:action_mailer, self)
+ ActiveSupport.run_load_hooks(:action_mailer, self)
end
end
diff --git a/actionmailer/lib/action_mailer/collector.rb b/actionmailer/lib/action_mailer/collector.rb
index e8883a8235..888410fa75 100644
--- a/actionmailer/lib/action_mailer/collector.rb
+++ b/actionmailer/lib/action_mailer/collector.rb
@@ -1,6 +1,8 @@
-require 'abstract_controller/collector'
-require 'active_support/core_ext/hash/reverse_merge'
-require 'active_support/core_ext/array/extract_options'
+# frozen_string_literal: true
+
+require "abstract_controller/collector"
+require "active_support/core_ext/hash/reverse_merge"
+require "active_support/core_ext/array/extract_options"
module ActionMailer
class Collector
diff --git a/actionmailer/lib/action_mailer/delivery_job.rb b/actionmailer/lib/action_mailer/delivery_job.rb
index 52772af2d3..40f26d8ad1 100644
--- a/actionmailer/lib/action_mailer/delivery_job.rb
+++ b/actionmailer/lib/action_mailer/delivery_job.rb
@@ -1,13 +1,36 @@
-require 'active_job'
+# frozen_string_literal: true
+
+require "active_job"
module ActionMailer
# The <tt>ActionMailer::DeliveryJob</tt> class is used when you
# want to send emails outside of the request-response cycle.
+ #
+ # Exceptions are rescued and handled by the mailer class.
class DeliveryJob < ActiveJob::Base # :nodoc:
queue_as { ActionMailer::Base.deliver_later_queue_name }
+ rescue_from StandardError, with: :handle_exception_with_mailer_class
+
def perform(mailer, mail_method, delivery_method, *args) #:nodoc:
mailer.constantize.public_send(mail_method, *args).send(delivery_method)
end
+
+ private
+ # "Deserialize" the mailer class name by hand in case another argument
+ # (like a Global ID reference) raised DeserializationError.
+ def mailer_class
+ if mailer = Array(@serialized_arguments).first || Array(arguments).first
+ mailer.constantize
+ end
+ end
+
+ def handle_exception_with_mailer_class(exception)
+ if klass = mailer_class
+ klass.handle_exception exception
+ else
+ raise exception
+ end
+ end
end
end
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index 4758b55a2a..5cd62307e6 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -1,4 +1,6 @@
-require 'tmpdir'
+# frozen_string_literal: true
+
+require "tmpdir"
module ActionMailer
# This module handles everything related to mail delivery, from registering
@@ -7,25 +9,18 @@ module ActionMailer
extend ActiveSupport::Concern
included do
- class_attribute :delivery_methods, :delivery_method
-
# Do not make this inheritable, because we always want it to propagate
- cattr_accessor :raise_delivery_errors
- self.raise_delivery_errors = true
-
- cattr_accessor :perform_deliveries
- self.perform_deliveries = true
-
- cattr_accessor :deliver_later_queue_name
- self.deliver_later_queue_name = :mailers
+ cattr_accessor :raise_delivery_errors, default: true
+ cattr_accessor :perform_deliveries, default: true
+ cattr_accessor :deliver_later_queue_name, default: :mailers
- self.delivery_methods = {}.freeze
- self.delivery_method = :smtp
+ class_attribute :delivery_methods, default: {}.freeze
+ class_attribute :delivery_method, default: :smtp
add_delivery_method :smtp, Mail::SMTP,
address: "localhost",
port: 25,
- domain: 'localhost.localdomain',
+ domain: "localhost.localdomain",
user_name: nil,
password: nil,
authentication: nil,
@@ -35,8 +30,8 @@ module ActionMailer
location: defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails"
add_delivery_method :sendmail, Mail::Sendmail,
- location: '/usr/sbin/sendmail',
- arguments: '-i -t'
+ location: "/usr/sbin/sendmail",
+ arguments: "-i"
add_delivery_method :test, Mail::TestMailer
end
@@ -51,15 +46,15 @@ module ActionMailer
#
# add_delivery_method :sendmail, Mail::Sendmail,
# location: '/usr/sbin/sendmail',
- # arguments: '-i -t'
- def add_delivery_method(symbol, klass, default_options={})
+ # arguments: '-i'
+ def add_delivery_method(symbol, klass, default_options = {})
class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings")
send(:"#{symbol}_settings=", default_options)
self.delivery_methods = delivery_methods.merge(symbol.to_sym => klass).freeze
end
- def wrap_delivery_behavior(mail, method=nil, options=nil) # :nodoc:
- method ||= self.delivery_method
+ def wrap_delivery_behavior(mail, method = nil, options = nil) # :nodoc:
+ method ||= delivery_method
mail.delivery_handler = self
case method
diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb
index b35d2ed965..72eb5d61e8 100644
--- a/actionmailer/lib/action_mailer/gem_version.rb
+++ b/actionmailer/lib/action_mailer/gem_version.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionMailer
# Returns the version of the currently loaded Action Mailer as a <tt>Gem::Version</tt>.
def self.gem_version
@@ -5,7 +7,7 @@ module ActionMailer
end
module VERSION
- MAJOR = 5
+ MAJOR = 6
MINOR = 0
TINY = 0
PRE = "alpha"
diff --git a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
index 6d02b39225..2b97ac5b94 100644
--- a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
+++ b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
@@ -1,9 +1,11 @@
-require 'base64'
+# frozen_string_literal: true
+
+require "base64"
module ActionMailer
# Implements a mailer preview interceptor that converts image tag src attributes
- # that use inline cid: style urls to data: style urls so that they are visible
- # when previewing a HTML email in a web browser.
+ # that use inline cid: style URLs to data: style URLs so that they are visible
+ # when previewing an HTML email in a web browser.
#
# This interceptor is enabled by default. To disable it, delete it from the
# <tt>ActionMailer::Base.preview_interceptors</tt> array:
@@ -11,7 +13,7 @@ module ActionMailer
# ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor)
#
class InlinePreviewInterceptor
- PATTERN = /src=(?:"cid:[^"]+"|'cid:[^']+')/i
+ PATTERN = /src=(?:"cid:[^"]+"|'cid:[^']+')/i
include Base64
@@ -26,7 +28,7 @@ module ActionMailer
def transform! #:nodoc:
return message if html_part.blank?
- html_source.gsub!(PATTERN) do |match|
+ html_part.body = html_part.decoded.gsub(PATTERN) do |match|
if part = find_part(match[9..-2])
%[src="#{data_url(part)}"]
else
@@ -38,24 +40,18 @@ module ActionMailer
end
private
- def message
- @message
- end
+ attr_reader :message
def html_part
@html_part ||= message.html_part
end
- def html_source
- html_part.body.raw_source
- end
-
def data_url(part)
"data:#{part.mime_type};base64,#{strict_encode64(part.body.raw_source)}"
end
def find_part(cid)
- message.all_parts.find{ |p| p.attachment? && p.cid == cid }
+ message.all_parts.find { |p| p.attachment? && p.cid == cid }
end
end
end
diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb
index 2867bf90fb..25c99342c2 100644
--- a/actionmailer/lib/action_mailer/log_subscriber.rb
+++ b/actionmailer/lib/action_mailer/log_subscriber.rb
@@ -1,4 +1,6 @@
-require 'active_support/log_subscriber'
+# frozen_string_literal: true
+
+require "active_support/log_subscriber"
module ActionMailer
# Implements the ActiveSupport::LogSubscriber for logging notifications when
@@ -7,8 +9,13 @@ module ActionMailer
# An email was delivered.
def deliver(event)
info do
- recipients = Array(event.payload[:to]).join(', ')
- "Sent mail to #{recipients} (#{event.duration.round(1)}ms)"
+ perform_deliveries = event.payload[:perform_deliveries]
+ recipients = Array(event.payload[:to]).join(", ")
+ if perform_deliveries
+ "Sent mail to #{recipients} (#{event.duration.round(1)}ms)"
+ else
+ "Skipped sending mail to #{recipients} as `perform_deliveries` is false"
+ end
end
debug { event.payload[:mail] }
diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb
index 239974e7b1..e7bed41f8d 100644
--- a/actionmailer/lib/action_mailer/mail_helper.rb
+++ b/actionmailer/lib/action_mailer/mail_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionMailer
# Provides helper methods for ActionMailer::Base that can be used for easily
# formatting messages, accessing mailer or message instances, and the
@@ -54,7 +56,7 @@ module ActionMailer
sentences = [[]]
text.split.each do |word|
- if sentences.first.present? && (sentences.last + [word]).join(' ').length > len
+ if sentences.first.present? && (sentences.last + [word]).join(" ").length > len
sentences << [word]
else
sentences.last << word
diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb
index 5fcb5a0c88..a57980c322 100644
--- a/actionmailer/lib/action_mailer/message_delivery.rb
+++ b/actionmailer/lib/action_mailer/message_delivery.rb
@@ -1,9 +1,10 @@
-require 'delegate'
+# frozen_string_literal: true
-module ActionMailer
+require "delegate"
+module ActionMailer
# The <tt>ActionMailer::MessageDelivery</tt> class is used by
- # <tt>ActionMailer::Base</tt> when creating a new mailer.
+ # ActionMailer::Base when creating a new mailer.
# <tt>MessageDelivery</tt> is a wrapper (+Delegator+ subclass) around a lazy
# created <tt>Mail::Message</tt>. You can get direct access to the
# <tt>Mail::Message</tt>, deliver the email or schedule the email to be sent
@@ -14,29 +15,35 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_later # enqueue email delivery as a job through Active Job
# Notifier.welcome(User.first).message # a Mail::Message object
class MessageDelivery < Delegator
- def initialize(mailer, mail_method, *args) #:nodoc:
- @mailer = mailer
- @mail_method = mail_method
- @args = args
+ def initialize(mailer_class, action, *args) #:nodoc:
+ @mailer_class, @action, @args = mailer_class, action, args
+
+ # The mail is only processed if we try to call any methods on it.
+ # Typical usage will leave it unloaded and call deliver_later.
+ @processed_mailer = nil
+ @mail_message = nil
end
+ # Method calls are delegated to the Mail::Message that's ready to deliver.
def __getobj__ #:nodoc:
- @obj ||= begin
- mailer = @mailer.new
- mailer.process @mail_method, *@args
- mailer.message
- end
+ @mail_message ||= processed_mailer.message
end
- def __setobj__(obj) #:nodoc:
- @obj = obj
+ # Unused except for delegator internals (dup, marshalling).
+ def __setobj__(mail_message) #:nodoc:
+ @mail_message = mail_message
end
- # Returns the Mail::Message object
+ # Returns the resulting Mail::Message
def message
__getobj__
end
+ # Was the delegate loaded, causing the mailer action to be processed?
+ def processed?
+ @processed_mailer || @mail_message
+ end
+
# Enqueues the email to be delivered through Active Job. When the
# job runs it will send the email using +deliver_now!+. That means
# that the message will be sent bypassing checking +perform_deliveries+
@@ -51,7 +58,15 @@ module ActionMailer
# * <tt>:wait</tt> - Enqueue the email to be delivered with a delay
# * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time
# * <tt>:queue</tt> - Enqueue the email on the specified queue
- def deliver_later!(options={})
+ #
+ # By default, the email will be enqueued using <tt>ActionMailer::DeliveryJob</tt>. Each
+ # <tt>ActionMailer::Base</tt> class can specify the job to use by setting the class variable
+ # +delivery_job+.
+ #
+ # class AccountRegistrationMailer < ApplicationMailer
+ # self.delivery_job = RegistrationDeliveryJob
+ # end
+ def deliver_later!(options = {})
enqueue_delivery :deliver_now!, options
end
@@ -67,7 +82,15 @@ module ActionMailer
# * <tt>:wait</tt> - Enqueue the email to be delivered with a delay.
# * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time.
# * <tt>:queue</tt> - Enqueue the email on the specified queue.
- def deliver_later(options={})
+ #
+ # By default, the email will be enqueued using <tt>ActionMailer::DeliveryJob</tt>. Each
+ # <tt>ActionMailer::Base</tt> class can specify the job to use by setting the class variable
+ # +delivery_job+.
+ #
+ # class AccountRegistrationMailer < ApplicationMailer
+ # self.delivery_job = RegistrationDeliveryJob
+ # end
+ def deliver_later(options = {})
enqueue_delivery :deliver_now, options
end
@@ -77,7 +100,9 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_now!
#
def deliver_now!
- message.deliver!
+ processed_mailer.handle_exceptions do
+ message.deliver!
+ end
end
# Delivers an email:
@@ -85,14 +110,35 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_now
#
def deliver_now
- message.deliver
+ processed_mailer.handle_exceptions do
+ message.deliver
+ end
end
private
+ # Returns the processed Mailer instance. We keep this instance
+ # on hand so we can delegate exception handling to it.
+ def processed_mailer
+ @processed_mailer ||= @mailer_class.new.tap do |mailer|
+ mailer.process @action, *@args
+ end
+ end
- def enqueue_delivery(delivery_method, options={})
- args = @mailer.name, @mail_method.to_s, delivery_method.to_s, *@args
- ActionMailer::DeliveryJob.set(options).perform_later(*args)
+ def enqueue_delivery(delivery_method, options = {})
+ if processed?
+ ::Kernel.raise "You've accessed the message before asking to " \
+ "deliver it later, so you may have made local changes that would " \
+ "be silently lost if we enqueued a job to deliver it. Why? Only " \
+ "the mailer method *arguments* are passed with the delivery job! " \
+ "Do not access the message in any way if you mean to deliver it " \
+ "later. Workarounds: 1. don't touch the message before calling " \
+ "#deliver_later, 2. only touch the message *within your mailer " \
+ "method*, or 3. use a custom Active Job instead of #deliver_later."
+ else
+ args = @mailer_class.name, @action.to_s, delivery_method.to_s, *@args
+ job = @mailer_class.delivery_job
+ job.set(options).perform_later(*args)
+ end
end
end
end
diff --git a/actionmailer/lib/action_mailer/parameterized.rb b/actionmailer/lib/action_mailer/parameterized.rb
new file mode 100644
index 0000000000..0fe417affe
--- /dev/null
+++ b/actionmailer/lib/action_mailer/parameterized.rb
@@ -0,0 +1,155 @@
+# frozen_string_literal: true
+
+module ActionMailer
+ # Provides the option to parameterize mailers in order to share instance variable
+ # setup, processing, and common headers.
+ #
+ # Consider this example that does not use parameterization:
+ #
+ # class InvitationsMailer < ApplicationMailer
+ # def account_invitation(inviter, invitee)
+ # @account = inviter.account
+ # @inviter = inviter
+ # @invitee = invitee
+ #
+ # subject = "#{@inviter.name} invited you to their Basecamp (#{@account.name})"
+ #
+ # mail \
+ # subject: subject,
+ # to: invitee.email_address,
+ # from: common_address(inviter),
+ # reply_to: inviter.email_address_with_name
+ # end
+ #
+ # def project_invitation(project, inviter, invitee)
+ # @account = inviter.account
+ # @project = project
+ # @inviter = inviter
+ # @invitee = invitee
+ # @summarizer = ProjectInvitationSummarizer.new(@project.bucket)
+ #
+ # subject = "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})"
+ #
+ # mail \
+ # subject: subject,
+ # to: invitee.email_address,
+ # from: common_address(inviter),
+ # reply_to: inviter.email_address_with_name
+ # end
+ #
+ # def bulk_project_invitation(projects, inviter, invitee)
+ # @account = inviter.account
+ # @projects = projects.sort_by(&:name)
+ # @inviter = inviter
+ # @invitee = invitee
+ #
+ # subject = "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})"
+ #
+ # mail \
+ # subject: subject,
+ # to: invitee.email_address,
+ # from: common_address(inviter),
+ # reply_to: inviter.email_address_with_name
+ # end
+ # end
+ #
+ # InvitationsMailer.account_invitation(person_a, person_b).deliver_later
+ #
+ # Using parameterized mailers, this can be rewritten as:
+ #
+ # class InvitationsMailer < ApplicationMailer
+ # before_action { @inviter, @invitee = params[:inviter], params[:invitee] }
+ # before_action { @account = params[:inviter].account }
+ #
+ # default to: -> { @invitee.email_address },
+ # from: -> { common_address(@inviter) },
+ # reply_to: -> { @inviter.email_address_with_name }
+ #
+ # def account_invitation
+ # mail subject: "#{@inviter.name} invited you to their Basecamp (#{@account.name})"
+ # end
+ #
+ # def project_invitation
+ # @project = params[:project]
+ # @summarizer = ProjectInvitationSummarizer.new(@project.bucket)
+ #
+ # mail subject: "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})"
+ # end
+ #
+ # def bulk_project_invitation
+ # @projects = params[:projects].sort_by(&:name)
+ #
+ # mail subject: "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})"
+ # end
+ # end
+ #
+ # InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later
+ module Parameterized
+ extend ActiveSupport::Concern
+
+ included do
+ attr_accessor :params
+ end
+
+ module ClassMethods
+ # Provide the parameters to the mailer in order to use them in the instance methods and callbacks.
+ #
+ # InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later
+ #
+ # See Parameterized documentation for full example.
+ def with(params)
+ ActionMailer::Parameterized::Mailer.new(self, params)
+ end
+ end
+
+ class Mailer # :nodoc:
+ def initialize(mailer, params)
+ @mailer, @params = mailer, params
+ end
+
+ private
+ def method_missing(method_name, *args)
+ if @mailer.action_methods.include?(method_name.to_s)
+ ActionMailer::Parameterized::MessageDelivery.new(@mailer, method_name, @params, *args)
+ else
+ super
+ end
+ end
+
+ def respond_to_missing?(method, include_all = false)
+ @mailer.respond_to?(method, include_all)
+ end
+ end
+
+ class MessageDelivery < ActionMailer::MessageDelivery # :nodoc:
+ def initialize(mailer_class, action, params, *args)
+ super(mailer_class, action, *args)
+ @params = params
+ end
+
+ private
+ def processed_mailer
+ @processed_mailer ||= @mailer_class.new.tap do |mailer|
+ mailer.params = @params
+ mailer.process @action, *@args
+ end
+ end
+
+ def enqueue_delivery(delivery_method, options = {})
+ if processed?
+ super
+ else
+ args = @mailer_class.name, @action.to_s, delivery_method.to_s, @params, *@args
+ job = @mailer_class.parameterized_delivery_job
+ job.set(options).perform_later(*args)
+ end
+ end
+ end
+
+ class DeliveryJob < ActionMailer::DeliveryJob # :nodoc:
+ def perform(mailer, mail_method, delivery_method, params, *args)
+ mailer.constantize.with(params).public_send(mail_method, *args).send(delivery_method)
+ end
+ end
+ end
+end
diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb
index aab92fe8db..500b3bede0 100644
--- a/actionmailer/lib/action_mailer/preview.rb
+++ b/actionmailer/lib/action_mailer/preview.rb
@@ -1,4 +1,6 @@
-require 'active_support/descendants_tracker'
+# frozen_string_literal: true
+
+require "active_support/descendants_tracker"
module ActionMailer
module Previews #:nodoc:
@@ -15,13 +17,12 @@ module ActionMailer
#
# config.action_mailer.show_previews = true
#
- # Defaults to true for development environment
+ # Defaults to +true+ for development environment
#
mattr_accessor :show_previews, instance_writer: false
# :nodoc:
- mattr_accessor :preview_interceptors, instance_writer: false
- self.preview_interceptors = [ActionMailer::InlinePreviewInterceptor]
+ mattr_accessor :preview_interceptors, instance_writer: false, default: [ActionMailer::InlinePreviewInterceptor]
end
module ClassMethods
@@ -30,27 +31,51 @@ module ActionMailer
interceptors.flatten.compact.each { |interceptor| register_preview_interceptor(interceptor) }
end
+ # Unregister one or more previously registered Interceptors.
+ def unregister_preview_interceptors(*interceptors)
+ interceptors.flatten.compact.each { |interceptor| unregister_preview_interceptor(interceptor) }
+ end
+
# Register an Interceptor which will be called before mail is previewed.
# Either a class or a string can be passed in as the Interceptor. If a
- # string is passed in it will be <tt>constantize</tt>d.
+ # string is passed in it will be constantized.
def register_preview_interceptor(interceptor)
- preview_interceptor = case interceptor
+ preview_interceptor = interceptor_class_for(interceptor)
+
+ unless preview_interceptors.include?(preview_interceptor)
+ preview_interceptors << preview_interceptor
+ end
+ end
+
+ # Unregister a previously registered Interceptor.
+ # Either a class or a string can be passed in as the Interceptor. If a
+ # string is passed in it will be constantized.
+ def unregister_preview_interceptor(interceptor)
+ preview_interceptors.delete(interceptor_class_for(interceptor))
+ end
+
+ private
+
+ def interceptor_class_for(interceptor)
+ case interceptor
when String, Symbol
interceptor.to_s.camelize.constantize
else
interceptor
end
-
- unless preview_interceptors.include?(preview_interceptor)
- preview_interceptors << preview_interceptor
end
- end
end
end
class Preview
extend ActiveSupport::DescendantsTracker
+ attr_reader :params
+
+ def initialize(params = {})
+ @params = params
+ end
+
class << self
# Returns all mailer preview classes.
def all
@@ -61,8 +86,8 @@ module ActionMailer
# Returns the mail object for the given email name. The registered preview
# interceptors will be informed so that they can transform the message
# as they would if the mail was actually being delivered.
- def call(email)
- preview = self.new
+ def call(email, params = {})
+ preview = new(params)
message = preview.public_send(email)
inform_preview_interceptors(message)
message
@@ -73,42 +98,42 @@ module ActionMailer
public_instance_methods(false).map(&:to_s).sort
end
- # Returns true if the email exists.
+ # Returns +true+ if the email exists.
def email_exists?(email)
emails.include?(email)
end
- # Returns true if the preview exists.
+ # Returns +true+ if the preview exists.
def exists?(preview)
- all.any?{ |p| p.preview_name == preview }
+ all.any? { |p| p.preview_name == preview }
end
# Find a mailer preview by its underscored class name.
def find(preview)
- all.find{ |p| p.preview_name == preview }
+ all.find { |p| p.preview_name == preview }
end
# Returns the underscored name of the mailer preview without the suffix.
def preview_name
- name.sub(/Preview$/, '').underscore
+ name.sub(/Preview$/, "").underscore
end
- protected
- def load_previews #:nodoc:
+ private
+ def load_previews
if preview_path
- Dir["#{preview_path}/**/*_preview.rb"].each{ |file| require_dependency file }
+ Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file }
end
end
- def preview_path #:nodoc:
+ def preview_path
Base.preview_path
end
- def show_previews #:nodoc:
+ def show_previews
Base.show_previews
end
- def inform_preview_interceptors(message) #:nodoc:
+ def inform_preview_interceptors(message)
Base.preview_interceptors.each do |interceptor|
interceptor.previewing_email(message)
end
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index fa707021c7..bb6141b406 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -1,4 +1,6 @@
-require 'active_job/railtie'
+# frozen_string_literal: true
+
+require "active_job/railtie"
require "action_mailer"
require "rails"
require "abstract_controller/railties/routes_helpers"
@@ -18,16 +20,17 @@ module ActionMailer
if app.config.force_ssl
options.default_url_options ||= {}
- options.default_url_options[:protocol] ||= 'https'
+ options.default_url_options[:protocol] ||= "https"
end
options.assets_dir ||= paths["public"].first
options.javascripts_dir ||= paths["public/javascripts"].first
options.stylesheets_dir ||= paths["public/stylesheets"].first
options.show_previews = Rails.env.development? if options.show_previews.nil?
+ options.cache_store ||= Rails.cache
if options.show_previews
- options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/mailers/previews" : nil
+ options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/mailers/previews" : nil
end
# make sure readers methods get compiled
@@ -43,14 +46,12 @@ module ActionMailer
register_preview_interceptors(options.delete(:preview_interceptors))
register_observers(options.delete(:observers))
- options.each { |k,v| send("#{k}=", v) }
+ options.each { |k, v| send("#{k}=", v) }
+ end
- if options.show_previews
- app.routes.prepend do
- get '/rails/mailers' => "rails/mailers#index"
- get '/rails/mailers/*path' => "rails/mailers#preview"
- end
- end
+ ActiveSupport.on_load(:action_dispatch_integration_test) do
+ include ActionMailer::TestHelper
+ include ActionMailer::TestCase::ClearTestDeliveries
end
end
@@ -60,9 +61,24 @@ module ActionMailer
end
end
- config.after_initialize do
- if ActionMailer::Base.preview_path
- ActiveSupport::Dependencies.autoload_paths << ActionMailer::Base.preview_path
+ initializer "action_mailer.eager_load_actions" do
+ ActiveSupport.on_load(:after_initialize) do
+ ActionMailer::Base.descendants.each(&:action_methods) if config.eager_load
+ end
+ end
+
+ config.after_initialize do |app|
+ options = app.config.action_mailer
+
+ if options.show_previews
+ app.routes.prepend do
+ get "/rails/mailers" => "rails/mailers#index", internal: true
+ get "/rails/mailers/*path" => "rails/mailers#preview", internal: true
+ end
+
+ if options.preview_path
+ ActiveSupport::Dependencies.autoload_paths << options.preview_path
+ end
end
end
end
diff --git a/actionmailer/lib/action_mailer/rescuable.rb b/actionmailer/lib/action_mailer/rescuable.rb
new file mode 100644
index 0000000000..5b567eb500
--- /dev/null
+++ b/actionmailer/lib/action_mailer/rescuable.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module ActionMailer #:nodoc:
+ # Provides +rescue_from+ for mailers. Wraps mailer action processing,
+ # mail job processing, and mail delivery.
+ module Rescuable
+ extend ActiveSupport::Concern
+ include ActiveSupport::Rescuable
+
+ class_methods do
+ def handle_exception(exception) #:nodoc:
+ rescue_with_handler(exception) || raise(exception)
+ end
+ end
+
+ def handle_exceptions #:nodoc:
+ yield
+ rescue => exception
+ rescue_with_handler(exception) || raise
+ end
+
+ private
+ def process(*)
+ handle_exceptions do
+ super
+ end
+ end
+ end
+end
diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb
index 0aa15e31ba..ee5a864847 100644
--- a/actionmailer/lib/action_mailer/test_case.rb
+++ b/actionmailer/lib/action_mailer/test_case.rb
@@ -1,16 +1,35 @@
-require 'active_support/test_case'
-require 'rails-dom-testing'
+# frozen_string_literal: true
+
+require "active_support/test_case"
+require "rails-dom-testing"
module ActionMailer
class NonInferrableMailerError < ::StandardError
def initialize(name)
- super "Unable to determine the mailer to test from #{name}. " +
- "You'll need to specify it using tests YourMailer in your " +
+ super "Unable to determine the mailer to test from #{name}. " \
+ "You'll need to specify it using tests YourMailer in your " \
"test case definition"
end
end
class TestCase < ActiveSupport::TestCase
+ module ClearTestDeliveries
+ extend ActiveSupport::Concern
+
+ included do
+ setup :clear_test_deliveries
+ teardown :clear_test_deliveries
+ end
+
+ private
+
+ def clear_test_deliveries
+ if ActionMailer::Base.delivery_method == :test
+ ActionMailer::Base.deliveries.clear
+ end
+ end
+ end
+
module Behavior
extend ActiveSupport::Concern
@@ -24,6 +43,7 @@ module ActionMailer
setup :initialize_test_deliveries
setup :set_expected_mail
teardown :restore_test_deliveries
+ ActiveSupport.run_load_hooks(:action_mailer_test_case, self)
end
module ClassMethods
@@ -39,7 +59,7 @@ module ActionMailer
end
def mailer_class
- if mailer = self._mailer_class
+ if mailer = _mailer_class
mailer
else
tests determine_default_mailer(name)
@@ -55,37 +75,36 @@ module ActionMailer
end
end
- protected
+ private
- def initialize_test_deliveries # :nodoc:
+ def initialize_test_deliveries
set_delivery_method :test
@old_perform_deliveries = ActionMailer::Base.perform_deliveries
ActionMailer::Base.perform_deliveries = true
+ ActionMailer::Base.deliveries.clear
end
- def restore_test_deliveries # :nodoc:
+ def restore_test_deliveries
restore_delivery_method
ActionMailer::Base.perform_deliveries = @old_perform_deliveries
- ActionMailer::Base.deliveries.clear
end
- def set_delivery_method(method) # :nodoc:
+ def set_delivery_method(method)
@old_delivery_method = ActionMailer::Base.delivery_method
ActionMailer::Base.delivery_method = method
end
- def restore_delivery_method # :nodoc:
+ def restore_delivery_method
+ ActionMailer::Base.deliveries.clear
ActionMailer::Base.delivery_method = @old_delivery_method
end
- def set_expected_mail # :nodoc:
+ def set_expected_mail
@expected = Mail.new
@expected.content_type ["text", "plain", { "charset" => charset }]
- @expected.mime_version = '1.0'
+ @expected.mime_version = "1.0"
end
- private
-
def charset
"UTF-8"
end
@@ -95,7 +114,7 @@ module ActionMailer
end
def read_fixture(action)
- IO.readlines(File.join(Rails.root, 'test', 'fixtures', self.class.mailer_class.name.underscore, action))
+ IO.readlines(File.join(Rails.root, "test", "fixtures", self.class.mailer_class.name.underscore, action))
end
end
diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb
index e423aac389..a4751916af 100644
--- a/actionmailer/lib/action_mailer/test_helper.rb
+++ b/actionmailer/lib/action_mailer/test_helper.rb
@@ -1,4 +1,6 @@
-require 'active_job'
+# frozen_string_literal: true
+
+require "active_job"
module ActionMailer
# Provides helper methods for testing Action Mailer, including #assert_emails
@@ -26,13 +28,13 @@ module ActionMailer
#
# assert_emails 2 do
# ContactMailer.welcome.deliver_now
- # ContactMailer.welcome.deliver_now
+ # ContactMailer.welcome.deliver_later
# end
# end
- def assert_emails(number)
+ def assert_emails(number, &block)
if block_given?
original_count = ActionMailer::Base.deliveries.size
- yield
+ perform_enqueued_jobs(only: [ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob], &block)
new_count = ActionMailer::Base.deliveries.size
assert_equal number, new_count - original_count, "#{number} emails expected, but #{new_count - original_count} were sent"
else
@@ -58,7 +60,7 @@ module ActionMailer
#
# Note: This assertion is simply a shortcut for:
#
- # assert_emails 0
+ # assert_emails 0, &block
def assert_no_emails(&block)
assert_emails 0, &block
end
@@ -88,7 +90,49 @@ module ActionMailer
# end
# end
def assert_enqueued_emails(number, &block)
- assert_enqueued_jobs number, only: ActionMailer::DeliveryJob, &block
+ assert_enqueued_jobs number, only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block
+ end
+
+ # Asserts that a specific email has been enqueued, optionally
+ # matching arguments.
+ #
+ # def test_email
+ # ContactMailer.welcome.deliver_later
+ # assert_enqueued_email_with ContactMailer, :welcome
+ # end
+ #
+ # def test_email_with_arguments
+ # ContactMailer.welcome("Hello", "Goodbye").deliver_later
+ # assert_enqueued_email_with ContactMailer, :welcome, args: ["Hello", "Goodbye"]
+ # end
+ #
+ # If a block is passed, that block should cause the specified email
+ # to be enqueued.
+ #
+ # def test_email_in_block
+ # assert_enqueued_email_with ContactMailer, :welcome do
+ # ContactMailer.welcome.deliver_later
+ # end
+ # end
+ #
+ # If +args+ is provided as a Hash, a parameterized email is matched.
+ #
+ # def test_parameterized_email
+ # assert_enqueued_email_with ContactMailer, :welcome,
+ # args: {email: 'user@example.com'} do
+ # ContactMailer.with(email: 'user@example.com').welcome.deliver_later
+ # end
+ # end
+ def assert_enqueued_email_with(mailer, method, args: nil, queue: "mailers", &block)
+ if args.is_a? Hash
+ job = ActionMailer::Parameterized::DeliveryJob
+ args = [mailer.to_s, method.to_s, "deliver_now", args]
+ else
+ job = ActionMailer::DeliveryJob
+ args = [mailer.to_s, method.to_s, "deliver_now", *args]
+ end
+
+ assert_enqueued_with(job: job, args: args, queue: queue, &block)
end
# Asserts that no emails are enqueued for later delivery.
@@ -107,7 +151,7 @@ module ActionMailer
# end
# end
def assert_no_enqueued_emails(&block)
- assert_no_enqueued_jobs only: ActionMailer::DeliveryJob, &block
+ assert_no_enqueued_jobs only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block
end
end
end
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index 06f80a8fdc..4549d6eb57 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -1,4 +1,6 @@
-require_relative 'gem_version'
+# frozen_string_literal: true
+
+require_relative "gem_version"
module ActionMailer
# Returns the version of the currently loaded Action Mailer as a
diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
index 3ec7d3d896..c37a74c762 100644
--- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
+++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
@@ -1,24 +1,37 @@
+# frozen_string_literal: true
+
module Rails
module Generators
class MailerGenerator < NamedBase
- source_root File.expand_path("../templates", __FILE__)
+ source_root File.expand_path("templates", __dir__)
argument :actions, type: :array, default: [], banner: "method method"
check_class_collision suffix: "Mailer"
def create_mailer_file
- template "mailer.rb", File.join('app/mailers', class_path, "#{file_name}_mailer.rb")
- if self.behavior == :invoke
- template "application_mailer.rb", 'app/mailers/application_mailer.rb'
+ template "mailer.rb", File.join("app/mailers", class_path, "#{file_name}_mailer.rb")
+
+ in_root do
+ if behavior == :invoke && !File.exist?(application_mailer_file_name)
+ template "application_mailer.rb", application_mailer_file_name
+ end
end
end
hook_for :template_engine, :test_framework
- protected
- def file_name
- @_file_name ||= super.gsub(/\_mailer/i, '')
+ private
+ def file_name # :doc:
+ @_file_name ||= super.sub(/_mailer\z/i, "")
+ end
+
+ def application_mailer_file_name
+ @_application_mailer_file_name ||= if mountable_engine?
+ "app/mailers/#{namespaced_path}/application_mailer.rb"
+ else
+ "app/mailers/application_mailer.rb"
+ end
end
end
end
diff --git a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb
deleted file mode 100644
index d25d8892dd..0000000000
--- a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-class ApplicationMailer < ActionMailer::Base
- default from: "from@example.com"
- layout 'mailer'
-end
diff --git a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb.tt b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb.tt
new file mode 100644
index 0000000000..00fb9bd48f
--- /dev/null
+++ b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb.tt
@@ -0,0 +1,6 @@
+<% module_namespacing do -%>
+class ApplicationMailer < ActionMailer::Base
+ default from: 'from@example.com'
+ layout 'mailer'
+end
+<% end %>
diff --git a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb.tt
index 348d314758..348d314758 100644
--- a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
+++ b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb.tt
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 285b2cfcb5..f647896374 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -1,26 +1,27 @@
-require File.expand_path('../../../load_paths', __FILE__)
-require 'active_support/core_ext/kernel/reporting'
+# frozen_string_literal: true
+
+require "active_support/core_ext/kernel/reporting"
# These are the normal settings that will be set up by Railties
# TODO: Have these tests support other combinations of these values
silence_warnings do
- Encoding.default_internal = "UTF-8"
- Encoding.default_external = "UTF-8"
+ Encoding.default_internal = Encoding::UTF_8
+ Encoding.default_external = Encoding::UTF_8
end
module Rails
def self.root
- File.expand_path('../', File.dirname(__FILE__))
+ File.expand_path("..", __dir__)
end
end
-require 'active_support/testing/autorun'
-require 'active_support/testing/method_call_assertions'
-require 'action_mailer'
-require 'action_mailer/test_case'
+require "active_support/testing/autorun"
+require "active_support/testing/method_call_assertions"
+require "action_mailer"
+require "action_mailer/test_case"
# Emulate AV railtie
-require 'action_view'
+require "action_view"
ActionMailer::Base.include(ActionView::Layouts)
# Show backtraces for deprecated behavior for quicker cleanup.
@@ -29,18 +30,20 @@ ActiveSupport::Deprecation.debug = true
# Disable available locale checks to avoid warnings running the test suite.
I18n.enforce_available_locales = false
-FIXTURE_LOAD_PATH = File.expand_path('fixtures', File.dirname(__FILE__))
+FIXTURE_LOAD_PATH = File.expand_path("fixtures", __dir__)
ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH
-# Skips the current run on Rubinius using Minitest::Assertions#skip
-def rubinius_skip(message = '')
- skip message if RUBY_ENGINE == 'rbx'
-end
-# Skips the current run on JRuby using Minitest::Assertions#skip
-def jruby_skip(message = '')
- skip message if defined?(JRUBY_VERSION)
-end
-
class ActiveSupport::TestCase
include ActiveSupport::Testing::MethodCallAssertions
+
+ private
+ # Skips the current run on Rubinius using Minitest::Assertions#skip
+ def rubinius_skip(message = "")
+ skip message if RUBY_ENGINE == "rbx"
+ end
+
+ # Skips the current run on JRuby using Minitest::Assertions#skip
+ def jruby_skip(message = "")
+ skip message if defined?(JRUBY_VERSION)
+ end
end
diff --git a/actionmailer/test/assert_select_email_test.rb b/actionmailer/test/assert_select_email_test.rb
index cae2e20abd..9699fe4000 100644
--- a/actionmailer/test/assert_select_email_test.rb
+++ b/actionmailer/test/assert_select_email_test.rb
@@ -1,4 +1,6 @@
-require 'abstract_unit'
+# frozen_string_literal: true
+
+require "abstract_unit"
class AssertSelectEmailTest < ActionMailer::TestCase
class AssertSelectMailer < ActionMailer::Base
@@ -11,8 +13,8 @@ class AssertSelectEmailTest < ActionMailer::TestCase
class AssertMultipartSelectMailer < ActionMailer::Base
def test(options)
mail subject: "Test e-mail", from: "test@test.host", to: "test <test@test.host>" do |format|
- format.text { render text: options[:text] }
- format.html { render text: options[:html] }
+ format.text { render plain: options[:text] }
+ format.html { render plain: options[:html] }
end
end
end
@@ -23,7 +25,7 @@ class AssertSelectEmailTest < ActionMailer::TestCase
def test_assert_select_email
assert_raise ActiveSupport::TestCase::Assertion do
- assert_select_email {}
+ assert_select_email { }
end
AssertSelectMailer.test("<div><p>foo</p><p>bar</p></div>").deliver_now
@@ -36,7 +38,7 @@ class AssertSelectEmailTest < ActionMailer::TestCase
end
def test_assert_select_email_multipart
- AssertMultipartSelectMailer.test(html: "<div><p>foo</p><p>bar</p></div>", text: 'foo bar').deliver_now
+ AssertMultipartSelectMailer.test(html: "<div><p>foo</p><p>bar</p></div>", text: "foo bar").deliver_now
assert_select_email do
assert_select "div:root" do
assert_select "p:first-child", "foo"
diff --git a/actionmailer/test/asset_host_test.rb b/actionmailer/test/asset_host_test.rb
index 10cfdcf693..9cd8cae88c 100644
--- a/actionmailer/test/asset_host_test.rb
+++ b/actionmailer/test/asset_host_test.rb
@@ -1,11 +1,13 @@
-require 'abstract_unit'
-require 'action_controller'
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "action_controller"
class AssetHostMailer < ActionMailer::Base
def email_with_asset
- mail to: 'test@localhost',
- subject: 'testing email containing asset path while asset_host is set',
- from: 'tester@example.com'
+ mail to: "test@localhost",
+ subject: "testing email containing asset path while asset_host is set",
+ from: "tester@example.com"
end
end
@@ -22,16 +24,16 @@ class AssetHostTest < ActionMailer::TestCase
def test_asset_host_as_string
mail = AssetHostMailer.email_with_asset
- assert_dom_equal %Q{<img alt="Somelogo" src="http://www.example.com/images/somelogo.png" />}, mail.body.to_s.strip
+ assert_dom_equal '<img src="http://www.example.com/images/somelogo.png" />', mail.body.to_s.strip
end
def test_asset_host_as_one_argument_proc
AssetHostMailer.config.asset_host = Proc.new { |source|
- if source.starts_with?('/images')
- 'http://images.example.com'
+ if source.starts_with?("/images")
+ "http://images.example.com"
end
}
mail = AssetHostMailer.email_with_asset
- assert_dom_equal %Q{<img alt="Somelogo" src="http://images.example.com/images/somelogo.png" />}, mail.body.to_s.strip
+ assert_dom_equal '<img src="http://images.example.com/images/somelogo.png" />', mail.body.to_s.strip
end
end
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index fe5358f209..86c0172772 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -1,12 +1,14 @@
-require 'abstract_unit'
-require 'set'
+# frozen_string_literal: true
-require 'action_dispatch'
-require 'active_support/time'
+require "abstract_unit"
+require "set"
-require 'mailers/base_mailer'
-require 'mailers/proc_mailer'
-require 'mailers/asset_mailer'
+require "action_dispatch"
+require "active_support/time"
+
+require "mailers/base_mailer"
+require "mailers/proc_mailer"
+require "mailers/asset_mailer"
class BaseTest < ActiveSupport::TestCase
include Rails::Dom::Testing::Assertions::DomAssertions
@@ -32,33 +34,33 @@ class BaseTest < ActiveSupport::TestCase
# Basic mail usage without block
test "mail() should set the headers of the mail message" do
email = BaseMailer.welcome
- assert_equal(['system@test.lindsaar.net'], email.to)
- assert_equal(['jose@test.plataformatec.com'], email.from)
- assert_equal('The first email on new API!', email.subject)
+ assert_equal(["system@test.lindsaar.net"], email.to)
+ assert_equal(["jose@test.plataformatec.com"], email.from)
+ assert_equal("The first email on new API!", email.subject)
end
test "mail() with from overwrites the class level default" do
- email = BaseMailer.welcome(from: 'someone@example.com',
- to: 'another@example.org')
- assert_equal(['someone@example.com'], email.from)
- assert_equal(['another@example.org'], email.to)
+ email = BaseMailer.welcome(from: "someone@example.com",
+ to: "another@example.org")
+ assert_equal(["someone@example.com"], email.from)
+ assert_equal(["another@example.org"], email.to)
end
test "mail() with bcc, cc, content_type, charset, mime_version, reply_to and date" do
time = Time.now.beginning_of_day.to_datetime
- email = BaseMailer.welcome(bcc: 'bcc@test.lindsaar.net',
- cc: 'cc@test.lindsaar.net',
- content_type: 'multipart/mixed',
- charset: 'iso-8559-1',
- mime_version: '2.0',
- reply_to: 'reply-to@test.lindsaar.net',
+ email = BaseMailer.welcome(bcc: "bcc@test.lindsaar.net",
+ cc: "cc@test.lindsaar.net",
+ content_type: "multipart/mixed",
+ charset: "iso-8559-1",
+ mime_version: "2.0",
+ reply_to: "reply-to@test.lindsaar.net",
date: time)
- assert_equal(['bcc@test.lindsaar.net'], email.bcc)
- assert_equal(['cc@test.lindsaar.net'], email.cc)
- assert_equal('multipart/mixed; charset=iso-8559-1', email.content_type)
- assert_equal('iso-8559-1', email.charset)
- assert_equal('2.0', email.mime_version)
- assert_equal(['reply-to@test.lindsaar.net'], email.reply_to)
+ assert_equal(["bcc@test.lindsaar.net"], email.bcc)
+ assert_equal(["cc@test.lindsaar.net"], email.cc)
+ assert_equal("multipart/mixed; charset=iso-8559-1", email.content_type)
+ assert_equal("iso-8559-1", email.charset)
+ assert_equal("2.0", email.mime_version)
+ assert_equal(["reply-to@test.lindsaar.net"], email.reply_to)
assert_equal(time, email.date)
end
@@ -75,63 +77,63 @@ class BaseTest < ActiveSupport::TestCase
test "should set template content type if mail has only one part" do
mail = BaseMailer.html_only
- assert_equal('text/html', mail.mime_type)
+ assert_equal("text/html", mail.mime_type)
mail = BaseMailer.plain_text_only
- assert_equal('text/plain', mail.mime_type)
+ assert_equal("text/plain", mail.mime_type)
end
# Custom headers
test "custom headers" do
email = BaseMailer.welcome
- assert_equal("Not SPAM", email['X-SPAM'].decoded)
+ assert_equal("Not SPAM", email["X-SPAM"].decoded)
end
test "can pass random headers in as a hash to mail" do
- hash = {'X-Special-Domain-Specific-Header' => "SecretValue",
- 'In-Reply-To' => '1234@mikel.me.com' }
+ hash = { "X-Special-Domain-Specific-Header" => "SecretValue",
+ "In-Reply-To" => "1234@mikel.me.com" }
mail = BaseMailer.welcome(hash)
- assert_equal('SecretValue', mail['X-Special-Domain-Specific-Header'].decoded)
- assert_equal('1234@mikel.me.com', mail['In-Reply-To'].decoded)
+ assert_equal("SecretValue", mail["X-Special-Domain-Specific-Header"].decoded)
+ assert_equal("1234@mikel.me.com", mail["In-Reply-To"].decoded)
end
test "can pass random headers in as a hash to headers" do
- hash = {'X-Special-Domain-Specific-Header' => "SecretValue",
- 'In-Reply-To' => '1234@mikel.me.com' }
+ hash = { "X-Special-Domain-Specific-Header" => "SecretValue",
+ "In-Reply-To" => "1234@mikel.me.com" }
mail = BaseMailer.welcome_with_headers(hash)
- assert_equal('SecretValue', mail['X-Special-Domain-Specific-Header'].decoded)
- assert_equal('1234@mikel.me.com', mail['In-Reply-To'].decoded)
+ assert_equal("SecretValue", mail["X-Special-Domain-Specific-Header"].decoded)
+ assert_equal("1234@mikel.me.com", mail["In-Reply-To"].decoded)
end
# Attachments
test "attachment with content" do
email = BaseMailer.attachment_with_content
assert_equal(1, email.attachments.length)
- assert_equal('invoice.pdf', email.attachments[0].filename)
- assert_equal('This is test File content', email.attachments['invoice.pdf'].decoded)
+ assert_equal("invoice.pdf", email.attachments[0].filename)
+ assert_equal("This is test File content", email.attachments["invoice.pdf"].decoded)
end
test "attachment gets content type from filename" do
email = BaseMailer.attachment_with_content
- assert_equal('invoice.pdf', email.attachments[0].filename)
- assert_equal('application/pdf', email.attachments[0].mime_type)
+ assert_equal("invoice.pdf", email.attachments[0].filename)
+ assert_equal("application/pdf", email.attachments[0].mime_type)
end
test "attachment with hash" do
email = BaseMailer.attachment_with_hash
assert_equal(1, email.attachments.length)
- assert_equal('invoice.jpg', email.attachments[0].filename)
- expected = "\312\213\254\232)b"
+ assert_equal("invoice.jpg", email.attachments[0].filename)
+ expected = +"\312\213\254\232)b"
expected.force_encoding(Encoding::BINARY)
- assert_equal expected, email.attachments['invoice.jpg'].decoded
+ assert_equal expected, email.attachments["invoice.jpg"].decoded
end
test "attachment with hash using default mail encoding" do
email = BaseMailer.attachment_with_hash_default_encoding
assert_equal(1, email.attachments.length)
- assert_equal('invoice.jpg', email.attachments[0].filename)
- expected = "\312\213\254\232)b"
+ assert_equal("invoice.jpg", email.attachments[0].filename)
+ expected = +"\312\213\254\232)b"
expected.force_encoding(Encoding::BINARY)
- assert_equal expected, email.attachments['invoice.jpg'].decoded
+ assert_equal expected, email.attachments["invoice.jpg"].decoded
end
test "sets mime type to multipart/mixed when attachment is included" do
@@ -140,14 +142,19 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("multipart/mixed", email.mime_type)
end
+ test "set mime type to text/html when attachment is included and body is set" do
+ email = BaseMailer.attachment_with_content(body: "Hello there", content_type: "text/html")
+ assert_equal("text/html", email.mime_type)
+ end
+
test "adds the rendered template as part" do
email = BaseMailer.attachment_with_content
assert_equal(2, email.parts.length)
assert_equal("multipart/mixed", email.mime_type)
assert_equal("text/html", email.parts[0].mime_type)
- assert_equal("Attachment with content", email.parts[0].body.encoded)
+ assert_equal("Attachment with content", email.parts[0].decoded)
assert_equal("application/pdf", email.parts[1].mime_type)
- assert_equal("VGhpcyBpcyB0ZXN0IEZpbGUgY29udGVudA==\r\n", email.parts[1].body.encoded)
+ assert_equal("This is test File content", email.parts[1].decoded)
end
test "adds the given :body as part" do
@@ -155,9 +162,9 @@ class BaseTest < ActiveSupport::TestCase
assert_equal(2, email.parts.length)
assert_equal("multipart/mixed", email.mime_type)
assert_equal("text/plain", email.parts[0].mime_type)
- assert_equal("I'm the eggman", email.parts[0].body.encoded)
+ assert_equal("I'm the eggman", email.parts[0].decoded)
assert_equal("application/pdf", email.parts[1].mime_type)
- assert_equal("VGhpcyBpcyB0ZXN0IEZpbGUgY29udGVudA==\r\n", email.parts[1].body.encoded)
+ assert_equal("This is test File content", email.parts[1].decoded)
end
test "can embed an inline attachment" do
@@ -215,24 +222,24 @@ class BaseTest < ActiveSupport::TestCase
email = BaseMailer.welcome(subject: nil)
assert_equal "Welcome", email.subject
- with_translation 'en', base_mailer: {welcome: {subject: "New Subject!"}} do
+ with_translation "en", base_mailer: { welcome: { subject: "New Subject!" } } do
email = BaseMailer.welcome(subject: nil)
assert_equal "New Subject!", email.subject
end
end
end
- test 'default subject can have interpolations' do
- with_translation 'en', base_mailer: {with_subject_interpolations: {subject: 'Will the real %{rapper_or_impersonator} please stand up?'}} do
+ test "default subject can have interpolations" do
+ with_translation "en", base_mailer: { with_subject_interpolations: { subject: "Will the real %{rapper_or_impersonator} please stand up?" } } do
email = BaseMailer.with_subject_interpolations
- assert_equal 'Will the real Slim Shady please stand up?', email.subject
+ assert_equal "Will the real Slim Shady please stand up?", email.subject
end
end
test "translations are scoped properly" do
- with_translation 'en', base_mailer: {email_with_translations: {greet_user: "Hello %{name}!"}} do
+ with_translation "en", base_mailer: { email_with_translations: { greet_user: "Hello %{name}!" } } do
email = BaseMailer.email_with_translations
- assert_equal 'Hello lifo!', email.body.encoded
+ assert_equal "Hello lifo!", email.body.encoded
end
end
@@ -240,7 +247,7 @@ class BaseTest < ActiveSupport::TestCase
class LateAttachmentMailer < ActionMailer::Base
def welcome
mail body: "yay", from: "welcome@example.com", to: "to@example.com"
- attachments['invoice.pdf'] = 'This is test File content'
+ attachments["invoice.pdf"] = "This is test File content"
end
end
@@ -252,7 +259,7 @@ class BaseTest < ActiveSupport::TestCase
class LateInlineAttachmentMailer < ActionMailer::Base
def welcome
mail body: "yay", from: "welcome@example.com", to: "to@example.com"
- attachments.inline['invoice.pdf'] = 'This is test File content'
+ attachments.inline["invoice.pdf"] = "This is test File content"
end
end
@@ -271,13 +278,13 @@ class BaseTest < ActiveSupport::TestCase
assert_nothing_raised { mail.message }
assert_equal ["image/jpeg; filename=controller_attachments.jpg",
- "image/jpeg; filename=attachments.jpg"], mail.attachments.inline.map {|a| a['Content-Type'].to_s }
+ "image/jpeg; filename=attachments.jpg"], mail.attachments.inline.map { |a| a["Content-Type"].to_s }
end
test "accessing attachments works after mail was called" do
class LateAttachmentAccessorMailer < ActionMailer::Base
def welcome
- attachments['invoice.pdf'] = 'This is test File content'
+ attachments["invoice.pdf"] = "This is test File content"
mail body: "yay", from: "welcome@example.com", to: "to@example.com"
unless attachments.map(&:filename) == ["invoice.pdf"]
@@ -315,22 +322,21 @@ class BaseTest < ActiveSupport::TestCase
test "implicit multipart with attachments creates nested parts" do
email = BaseMailer.implicit_multipart(attachments: true)
- assert_equal("application/pdf", email.parts[0].mime_type)
- assert_equal("multipart/alternative", email.parts[1].mime_type)
- assert_equal("text/plain", email.parts[1].parts[0].mime_type)
- assert_equal("TEXT Implicit Multipart", email.parts[1].parts[0].body.encoded)
- assert_equal("text/html", email.parts[1].parts[1].mime_type)
- assert_equal("HTML Implicit Multipart", email.parts[1].parts[1].body.encoded)
+ assert_equal(%w[ application/pdf multipart/alternative ], email.parts.map(&:mime_type).sort)
+ multipart = email.parts.detect { |p| p.mime_type == "multipart/alternative" }
+ assert_equal("text/plain", multipart.parts[0].mime_type)
+ assert_equal("TEXT Implicit Multipart", multipart.parts[0].body.encoded)
+ assert_equal("text/html", multipart.parts[1].mime_type)
+ assert_equal("HTML Implicit Multipart", multipart.parts[1].body.encoded)
end
test "implicit multipart with attachments and sort order" do
order = ["text/html", "text/plain"]
with_default BaseMailer, parts_order: order do
email = BaseMailer.implicit_multipart(attachments: true)
- assert_equal("application/pdf", email.parts[0].mime_type)
- assert_equal("multipart/alternative", email.parts[1].mime_type)
- assert_equal("text/plain", email.parts[1].parts[1].mime_type)
- assert_equal("text/html", email.parts[1].parts[0].mime_type)
+ assert_equal(%w[ application/pdf multipart/alternative ], email.parts.map(&:mime_type).sort)
+ multipart = email.parts.detect { |p| p.mime_type == "multipart/alternative" }
+ assert_equal(%w[ text/html text/plain ], multipart.parts.map(&:mime_type).sort)
end
end
@@ -366,7 +372,7 @@ class BaseTest < ActiveSupport::TestCase
I18n.backend = fallback_backend.new
I18n.fallbacks[:"de-AT"] = [:de]
- swap I18n, locale: 'de-AT' do
+ swap I18n, locale: "de-AT" do
email = BaseMailer.implicit_with_locale
assert_equal(2, email.parts.size)
assert_equal("multipart/alternative", email.mime_type)
@@ -380,7 +386,6 @@ class BaseTest < ActiveSupport::TestCase
end
end
-
test "implicit multipart with several view paths uses the first one with template" do
old = BaseMailer.view_paths
begin
@@ -421,12 +426,12 @@ class BaseTest < ActiveSupport::TestCase
test "explicit multipart with attachments creates nested parts" do
email = BaseMailer.explicit_multipart(attachments: true)
- assert_equal("application/pdf", email.parts[0].mime_type)
- assert_equal("multipart/alternative", email.parts[1].mime_type)
- assert_equal("text/plain", email.parts[1].parts[0].mime_type)
- assert_equal("TEXT Explicit Multipart", email.parts[1].parts[0].body.encoded)
- assert_equal("text/html", email.parts[1].parts[1].mime_type)
- assert_equal("HTML Explicit Multipart", email.parts[1].parts[1].body.encoded)
+ assert_equal(%w[ application/pdf multipart/alternative ], email.parts.map(&:mime_type).sort)
+ multipart = email.parts.detect { |p| p.mime_type == "multipart/alternative" }
+ assert_equal("text/plain", multipart.parts[0].mime_type)
+ assert_equal("TEXT Explicit Multipart", multipart.parts[0].body.encoded)
+ assert_equal("text/html", multipart.parts[1].mime_type)
+ assert_equal("HTML Explicit Multipart", multipart.parts[1].body.encoded)
end
test "explicit multipart with templates" do
@@ -449,7 +454,7 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("Format with any!", email.parts[1].body.encoded)
end
- test 'explicit without specifying format with format.any' do
+ test "explicit without specifying format with format.any" do
error = assert_raises(ArgumentError) do
BaseMailer.explicit_without_specifying_format_with_any.parts
end
@@ -501,20 +506,20 @@ class BaseTest < ActiveSupport::TestCase
test "should respond to action methods" do
assert_respond_to BaseMailer, :welcome
assert_respond_to BaseMailer, :implicit_multipart
- assert !BaseMailer.respond_to?(:mail)
- assert !BaseMailer.respond_to?(:headers)
+ assert_not_respond_to BaseMailer, :mail
+ assert_not_respond_to BaseMailer, :headers
end
test "calling just the action should return the generated mail object" do
email = BaseMailer.welcome
assert_equal(0, BaseMailer.deliveries.length)
- assert_equal('The first email on new API!', email.subject)
+ assert_equal("The first email on new API!", email.subject)
end
test "calling deliver on the action should deliver the mail object" do
assert_called(BaseMailer, :deliver_mail) do
mail = BaseMailer.welcome.deliver_now
- assert_equal 'The first email on new API!', mail.subject
+ assert_equal "The first email on new API!", mail.subject
end
end
@@ -534,41 +539,41 @@ class BaseTest < ActiveSupport::TestCase
# Rendering
test "you can specify a different template for implicit render" do
- mail = BaseMailer.implicit_different_template('implicit_multipart').deliver_now
+ mail = BaseMailer.implicit_different_template("implicit_multipart").deliver_now
assert_equal("HTML Implicit Multipart", mail.html_part.body.decoded)
assert_equal("TEXT Implicit Multipart", mail.text_part.body.decoded)
end
test "you can specify a different template for multipart render" do
- mail = BaseMailer.implicit_different_template_with_block('explicit_multipart_templates').deliver
+ mail = BaseMailer.implicit_different_template_with_block("explicit_multipart_templates").deliver
assert_equal("HTML Explicit Multipart Templates", mail.html_part.body.decoded)
assert_equal("TEXT Explicit Multipart Templates", mail.text_part.body.decoded)
end
test "should raise if missing template in implicit render" do
assert_raises ActionView::MissingTemplate do
- BaseMailer.implicit_different_template('missing_template').deliver_now
+ BaseMailer.implicit_different_template("missing_template").deliver_now
end
assert_equal(0, BaseMailer.deliveries.length)
end
test "you can specify a different template for explicit render" do
- mail = BaseMailer.explicit_different_template('explicit_multipart_templates').deliver_now
+ mail = BaseMailer.explicit_different_template("explicit_multipart_templates").deliver_now
assert_equal("HTML Explicit Multipart Templates", mail.html_part.body.decoded)
assert_equal("TEXT Explicit Multipart Templates", mail.text_part.body.decoded)
end
test "you can specify a different layout" do
- mail = BaseMailer.different_layout('different_layout').deliver_now
+ mail = BaseMailer.different_layout("different_layout").deliver_now
assert_equal("HTML -- HTML", mail.html_part.body.decoded)
assert_equal("PLAIN -- PLAIN", mail.text_part.body.decoded)
end
test "you can specify the template path for implicit lookup" do
- mail = BaseMailer.welcome_from_another_path('another.path/base_mailer').deliver_now
+ mail = BaseMailer.welcome_from_another_path("another.path/base_mailer").deliver_now
assert_equal("Welcome from another path", mail.body.encoded)
- mail = BaseMailer.welcome_from_another_path(['unknown/invalid', 'another.path/base_mailer']).deliver_now
+ mail = BaseMailer.welcome_from_another_path(["unknown/invalid", "another.path/base_mailer"]).deliver_now
assert_equal("Welcome from another path", mail.body.encoded)
end
@@ -578,7 +583,7 @@ class BaseTest < ActiveSupport::TestCase
mail = AssetMailer.welcome
- assert_dom_equal(%{<img alt="Dummy" src="http://global.com/images/dummy.png" />}, mail.body.to_s.strip)
+ assert_dom_equal(%{<img src="http://global.com/images/dummy.png" />}, mail.body.to_s.strip)
end
test "assets tags should use a Mailer's asset_host settings when available" do
@@ -592,18 +597,18 @@ class BaseTest < ActiveSupport::TestCase
mail = TempAssetMailer.welcome
- assert_dom_equal(%{<img alt="Dummy" src="http://local.com/images/dummy.png" />}, mail.body.to_s.strip)
+ assert_dom_equal(%{<img src="http://local.com/images/dummy.png" />}, mail.body.to_s.strip)
end
- test 'the view is not rendered when mail was never called' do
+ test "the view is not rendered when mail was never called" do
mail = BaseMailer.without_mail_call
- assert_equal('', mail.body.to_s.strip)
+ assert_equal("", mail.body.to_s.strip)
mail.deliver_now
end
- test 'the return value of mailer methods is not relevant' do
+ test "the return value of mailer methods is not relevant" do
mail = BaseMailer.with_nil_as_return_value
- assert_equal('Welcome', mail.body.to_s.strip)
+ assert_equal("Welcome", mail.body.to_s.strip)
mail.deliver_now
end
@@ -619,37 +624,52 @@ class BaseTest < ActiveSupport::TestCase
end
end
- test "you can register an observer to the mail object that gets informed on email delivery" do
+ test "you can register and unregister an observer to the mail object that gets informed on email delivery" do
mail_side_effects do
ActionMailer::Base.register_observer(MyObserver)
mail = BaseMailer.welcome
assert_called_with(MyObserver, :delivered_email, [mail]) do
mail.deliver_now
end
+
+ ActionMailer::Base.unregister_observer(MyObserver)
+ assert_not_called(MyObserver, :delivered_email, returns: mail) do
+ mail.deliver_now
+ end
end
end
- test "you can register an observer using its stringified name to the mail object that gets informed on email delivery" do
+ test "you can register and unregister an observer using its stringified name to the mail object that gets informed on email delivery" do
mail_side_effects do
ActionMailer::Base.register_observer("BaseTest::MyObserver")
mail = BaseMailer.welcome
assert_called_with(MyObserver, :delivered_email, [mail]) do
mail.deliver_now
end
+
+ ActionMailer::Base.unregister_observer("BaseTest::MyObserver")
+ assert_not_called(MyObserver, :delivered_email, returns: mail) do
+ mail.deliver_now
+ end
end
end
- test "you can register an observer using its symbolized underscored name to the mail object that gets informed on email delivery" do
+ test "you can register and unregister an observer using its symbolized underscored name to the mail object that gets informed on email delivery" do
mail_side_effects do
ActionMailer::Base.register_observer(:"base_test/my_observer")
mail = BaseMailer.welcome
assert_called_with(MyObserver, :delivered_email, [mail]) do
mail.deliver_now
end
+
+ ActionMailer::Base.unregister_observer(:"base_test/my_observer")
+ assert_not_called(MyObserver, :delivered_email, returns: mail) do
+ mail.deliver_now
+ end
end
end
- test "you can register multiple observers to the mail object that both get informed on email delivery" do
+ test "you can register and unregister multiple observers to the mail object that both get informed on email delivery" do
mail_side_effects do
ActionMailer::Base.register_observers("BaseTest::MyObserver", MySecondObserver)
mail = BaseMailer.welcome
@@ -658,6 +678,14 @@ class BaseTest < ActiveSupport::TestCase
mail.deliver_now
end
end
+
+ ActionMailer::Base.unregister_observers("BaseTest::MyObserver", MySecondObserver)
+ assert_not_called(MyObserver, :delivered_email, returns: mail) do
+ mail.deliver_now
+ end
+ assert_not_called(MySecondObserver, :delivered_email, returns: mail) do
+ mail.deliver_now
+ end
end
end
@@ -671,37 +699,52 @@ class BaseTest < ActiveSupport::TestCase
def self.previewing_email(mail); end
end
- test "you can register an interceptor to the mail object that gets passed the mail object before delivery" do
+ test "you can register and unregister an interceptor to the mail object that gets passed the mail object before delivery" do
mail_side_effects do
ActionMailer::Base.register_interceptor(MyInterceptor)
mail = BaseMailer.welcome
assert_called_with(MyInterceptor, :delivering_email, [mail]) do
mail.deliver_now
end
+
+ ActionMailer::Base.unregister_interceptor(MyInterceptor)
+ assert_not_called(MyInterceptor, :delivering_email, returns: mail) do
+ mail.deliver_now
+ end
end
end
- test "you can register an interceptor using its stringified name to the mail object that gets passed the mail object before delivery" do
+ test "you can register and unregister an interceptor using its stringified name to the mail object that gets passed the mail object before delivery" do
mail_side_effects do
ActionMailer::Base.register_interceptor("BaseTest::MyInterceptor")
mail = BaseMailer.welcome
assert_called_with(MyInterceptor, :delivering_email, [mail]) do
mail.deliver_now
end
+
+ ActionMailer::Base.unregister_interceptor("BaseTest::MyInterceptor")
+ assert_not_called(MyInterceptor, :delivering_email, returns: mail) do
+ mail.deliver_now
+ end
end
end
- test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before delivery" do
+ test "you can register and unregister an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before delivery" do
mail_side_effects do
ActionMailer::Base.register_interceptor(:"base_test/my_interceptor")
mail = BaseMailer.welcome
assert_called_with(MyInterceptor, :delivering_email, [mail]) do
mail.deliver_now
end
+
+ ActionMailer::Base.unregister_interceptor(:"base_test/my_interceptor")
+ assert_not_called(MyInterceptor, :delivering_email, returns: mail) do
+ mail.deliver_now
+ end
end
end
- test "you can register multiple interceptors to the mail object that both get passed the mail object before delivery" do
+ test "you can register and unregister multiple interceptors to the mail object that both get passed the mail object before delivery" do
mail_side_effects do
ActionMailer::Base.register_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
mail = BaseMailer.welcome
@@ -710,20 +753,37 @@ class BaseTest < ActiveSupport::TestCase
mail.deliver_now
end
end
+
+ ActionMailer::Base.unregister_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
+ assert_not_called(MyInterceptor, :delivering_email, returns: mail) do
+ mail.deliver_now
+ end
+ assert_not_called(MySecondInterceptor, :delivering_email, returns: mail) do
+ mail.deliver_now
+ end
end
end
test "being able to put proc's into the defaults hash and they get evaluated on mail sending" do
- mail1 = ProcMailer.welcome['X-Proc-Method']
+ mail1 = ProcMailer.welcome["X-Proc-Method"]
yesterday = 1.day.ago
Time.stub(:now, yesterday) do
- mail2 = ProcMailer.welcome['X-Proc-Method']
+ mail2 = ProcMailer.welcome["X-Proc-Method"]
assert(mail1.to_s.to_i > mail2.to_s.to_i)
end
end
- test 'default values which have to_proc (e.g. symbols) should not be considered procs' do
- assert(ProcMailer.welcome['x-has-to-proc'].to_s == 'symbol')
+ test "default values which have to_proc (e.g. symbols) should not be considered procs" do
+ assert(ProcMailer.welcome["x-has-to-proc"].to_s == "symbol")
+ end
+
+ test "proc default values can have arity of 1 where arg is a mailer instance" do
+ assert_equal(ProcMailer.welcome["X-Lambda-Arity-1-arg"].to_s, "complex_value")
+ assert_equal(ProcMailer.welcome["X-Lambda-Arity-1-self"].to_s, "complex_value")
+ end
+
+ test "proc default values with fixed arity of 0 can be called" do
+ assert_equal("0", ProcMailer.welcome["X-Lambda-Arity-0"].to_s)
end
test "we can call other defined methods on the class as needed" do
@@ -738,12 +798,12 @@ class BaseTest < ActiveSupport::TestCase
def welcome ; mail ; end
private
- def add_special_header!
- headers('X-Special-Header' => 'Wow, so special')
- end
+ def add_special_header!
+ headers("X-Special-Header" => "Wow, so special")
+ end
end
- assert_equal('Wow, so special', BeforeActionMailer.welcome['X-Special-Header'].to_s)
+ assert_equal("Wow, so special", BeforeActionMailer.welcome["X-Special-Header"].to_s)
end
test "modifying the mail message with an after_action" do
@@ -753,12 +813,12 @@ class BaseTest < ActiveSupport::TestCase
def welcome ; mail ; end
private
- def add_special_header!
- headers('X-Special-Header' => 'Testing')
- end
+ def add_special_header!
+ headers("X-Special-Header" => "Testing")
+ end
end
- assert_equal('Testing', AfterActionMailer.welcome['X-Special-Header'].to_s)
+ assert_equal("Testing", AfterActionMailer.welcome["X-Special-Header"].to_s)
end
test "adding an inline attachment using a before_action" do
@@ -768,19 +828,19 @@ class BaseTest < ActiveSupport::TestCase
def welcome ; mail ; end
private
- def add_inline_attachment!
- attachments.inline["footer.jpg"] = 'hey there'
- end
+ def add_inline_attachment!
+ attachments.inline["footer.jpg"] = "hey there"
+ end
end
mail = DefaultInlineAttachmentMailer.welcome
- assert_equal('image/jpeg; filename=footer.jpg', mail.attachments.inline.first['Content-Type'].to_s)
+ assert_equal("image/jpeg; filename=footer.jpg", mail.attachments.inline.first["Content-Type"].to_s)
end
test "action methods should be refreshed after defining new method" do
class FooMailer < ActionMailer::Base
- # this triggers action_methods
- self.respond_to?(:foo)
+ # This triggers action_methods.
+ respond_to?(:foo)
def notify
end
@@ -804,8 +864,8 @@ class BaseTest < ActiveSupport::TestCase
test "default_from can be set" do
class DefaultFromMailer < ActionMailer::Base
- default to: 'system@test.lindsaar.net'
- self.default_options = {from: "robert.pankowecki@gmail.com"}
+ default to: "system@test.lindsaar.net"
+ self.default_options = { from: "robert.pankowecki@gmail.com" }
def welcome
mail(subject: "subject", body: "hello world")
@@ -820,7 +880,7 @@ class BaseTest < ActiveSupport::TestCase
after_action :a_callback
def welcome
- headers('X-Special-Header' => 'special indeed!')
+ headers("X-Special-Header" => "special indeed!")
mail subject: "subject", body: "hello world", to: ["joe@example.com"]
end
@@ -836,7 +896,26 @@ class BaseTest < ActiveSupport::TestCase
assert_equal "special indeed!", mail["X-Special-Header"].to_s
end
- protected
+ test "notification for process" do
+ begin
+ events = []
+ ActiveSupport::Notifications.subscribe("process.action_mailer") do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+
+ BaseMailer.welcome(body: "Hello there").deliver_now
+
+ assert_equal 1, events.length
+ assert_equal "process.action_mailer", events[0].name
+ assert_equal "BaseMailer", events[0].payload[:mailer]
+ assert_equal :welcome, events[0].payload[:action]
+ assert_equal [{ body: "Hello there" }], events[0].payload[:args]
+ ensure
+ ActiveSupport::Notifications.unsubscribe "process.action_mailer"
+ end
+ end
+
+ private
# Execute the block setting the given values and restoring old values after
# the block is executed.
@@ -861,8 +940,6 @@ class BaseTest < ActiveSupport::TestCase
klass.default_params = old
end
- # A simple hack to restore the observers and interceptors for Mail, as it
- # does not have an unregister API yet.
def mail_side_effects
old_observers = Mail.class_variable_get(:@@delivery_notification_observers)
old_delivery_interceptors = Mail.class_variable_get(:@@delivery_interceptors)
@@ -901,7 +978,7 @@ class BasePreviewInterceptorsTest < ActiveSupport::TestCase
def self.previewing_email(mail); end
end
- test "you can register a preview interceptor to the mail object that gets passed the mail object before previewing" do
+ test "you can register and unregister a preview interceptor to the mail object that gets passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptor(MyInterceptor)
mail = BaseMailer.welcome
stub_any_instance(BaseMailerPreview) do |instance|
@@ -911,9 +988,14 @@ class BasePreviewInterceptorsTest < ActiveSupport::TestCase
end
end
end
+
+ ActionMailer::Base.unregister_preview_interceptor(MyInterceptor)
+ assert_not_called(MyInterceptor, :previewing_email, returns: mail) do
+ BaseMailerPreview.call(:welcome)
+ end
end
- test "you can register a preview interceptor using its stringified name to the mail object that gets passed the mail object before previewing" do
+ test "you can register and unregister a preview interceptor using its stringified name to the mail object that gets passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptor("BasePreviewInterceptorsTest::MyInterceptor")
mail = BaseMailer.welcome
stub_any_instance(BaseMailerPreview) do |instance|
@@ -923,9 +1005,14 @@ class BasePreviewInterceptorsTest < ActiveSupport::TestCase
end
end
end
+
+ ActionMailer::Base.unregister_preview_interceptor("BasePreviewInterceptorsTest::MyInterceptor")
+ assert_not_called(MyInterceptor, :previewing_email, returns: mail) do
+ BaseMailerPreview.call(:welcome)
+ end
end
- test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before previewing" do
+ test "you can register and unregister a preview interceptor using its symbolized underscored name to the mail object that gets passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptor(:"base_preview_interceptors_test/my_interceptor")
mail = BaseMailer.welcome
stub_any_instance(BaseMailerPreview) do |instance|
@@ -935,9 +1022,14 @@ class BasePreviewInterceptorsTest < ActiveSupport::TestCase
end
end
end
+
+ ActionMailer::Base.unregister_preview_interceptor(:"base_preview_interceptors_test/my_interceptor")
+ assert_not_called(MyInterceptor, :previewing_email, returns: mail) do
+ BaseMailerPreview.call(:welcome)
+ end
end
- test "you can register multiple preview interceptors to the mail object that both get passed the mail object before previewing" do
+ test "you can register and unregister multiple preview interceptors to the mail object that both get passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptors("BasePreviewInterceptorsTest::MyInterceptor", MySecondInterceptor)
mail = BaseMailer.welcome
stub_any_instance(BaseMailerPreview) do |instance|
@@ -949,5 +1041,28 @@ class BasePreviewInterceptorsTest < ActiveSupport::TestCase
end
end
end
+
+ ActionMailer::Base.unregister_preview_interceptors("BasePreviewInterceptorsTest::MyInterceptor", MySecondInterceptor)
+ assert_not_called(MyInterceptor, :previewing_email, returns: mail) do
+ BaseMailerPreview.call(:welcome)
+ end
+ assert_not_called(MySecondInterceptor, :previewing_email, returns: mail) do
+ BaseMailerPreview.call(:welcome)
+ end
+ end
+end
+
+class BasePreviewTest < ActiveSupport::TestCase
+ class BaseMailerPreview < ActionMailer::Preview
+ def welcome
+ BaseMailer.welcome(params)
+ end
+ end
+
+ test "has access to params" do
+ params = { name: "World" }
+
+ message = BaseMailerPreview.call(:welcome, params)
+ assert_equal "World", message["name"].decoded
end
end
diff --git a/actionmailer/test/caching_test.rb b/actionmailer/test/caching_test.rb
new file mode 100644
index 0000000000..22f310f39f
--- /dev/null
+++ b/actionmailer/test/caching_test.rb
@@ -0,0 +1,271 @@
+# frozen_string_literal: true
+
+require "fileutils"
+require "abstract_unit"
+require "mailers/base_mailer"
+require "mailers/caching_mailer"
+
+CACHE_DIR = "test_cache"
+# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
+FILE_STORE_PATH = File.join(__dir__, "/../temp/", CACHE_DIR)
+
+class FragmentCachingMailer < ActionMailer::Base
+ abstract!
+
+ def some_action; end
+end
+
+class BaseCachingTest < ActiveSupport::TestCase
+ def setup
+ super
+ @store = ActiveSupport::Cache::MemoryStore.new
+ @mailer = FragmentCachingMailer.new
+ @mailer.perform_caching = true
+ @mailer.cache_store = @store
+ end
+end
+
+class FragmentCachingTest < BaseCachingTest
+ def test_read_fragment_with_caching_enabled
+ @store.write("views/name", "value")
+ assert_equal "value", @mailer.read_fragment("name")
+ end
+
+ def test_read_fragment_with_caching_disabled
+ @mailer.perform_caching = false
+ @store.write("views/name", "value")
+ assert_nil @mailer.read_fragment("name")
+ end
+
+ def test_fragment_exist_with_caching_enabled
+ @store.write("views/name", "value")
+ assert @mailer.fragment_exist?("name")
+ assert_not @mailer.fragment_exist?("other_name")
+ end
+
+ def test_fragment_exist_with_caching_disabled
+ @mailer.perform_caching = false
+ @store.write("views/name", "value")
+ assert_not @mailer.fragment_exist?("name")
+ assert_not @mailer.fragment_exist?("other_name")
+ end
+
+ def test_write_fragment_with_caching_enabled
+ assert_nil @store.read("views/name")
+ assert_equal "value", @mailer.write_fragment("name", "value")
+ assert_equal "value", @store.read("views/name")
+ end
+
+ def test_write_fragment_with_caching_disabled
+ assert_nil @store.read("views/name")
+ @mailer.perform_caching = false
+ assert_equal "value", @mailer.write_fragment("name", "value")
+ assert_nil @store.read("views/name")
+ end
+
+ def test_expire_fragment_with_simple_key
+ @store.write("views/name", "value")
+ @mailer.expire_fragment "name"
+ assert_nil @store.read("views/name")
+ end
+
+ def test_expire_fragment_with_regexp
+ @store.write("views/name", "value")
+ @store.write("views/another_name", "another_value")
+ @store.write("views/primalgrasp", "will not expire ;-)")
+
+ @mailer.expire_fragment(/name/)
+
+ assert_nil @store.read("views/name")
+ assert_nil @store.read("views/another_name")
+ assert_equal "will not expire ;-)", @store.read("views/primalgrasp")
+ end
+
+ def test_fragment_for
+ @store.write("views/expensive", "fragment content")
+ fragment_computed = false
+
+ view_context = @mailer.view_context
+
+ buffer = "generated till now -> ".html_safe
+ buffer << view_context.send(:fragment_for, "expensive") { fragment_computed = true }
+
+ assert_not fragment_computed
+ assert_equal "generated till now -> fragment content", buffer
+ end
+
+ def test_html_safety
+ assert_nil @store.read("views/name")
+ content = "value".html_safe
+ assert_equal content, @mailer.write_fragment("name", content)
+
+ cached = @store.read("views/name")
+ assert_equal content, cached
+ assert_equal String, cached.class
+
+ html_safe = @mailer.read_fragment("name")
+ assert_equal content, html_safe
+ assert_predicate html_safe, :html_safe?
+ end
+end
+
+class FunctionalFragmentCachingTest < BaseCachingTest
+ def setup
+ super
+ @store = ActiveSupport::Cache::MemoryStore.new
+ @mailer = CachingMailer.new
+ @mailer.perform_caching = true
+ @mailer.cache_store = @store
+ end
+
+ def test_fragment_caching
+ email = @mailer.fragment_cache
+ expected_body = "\"Welcome\""
+
+ assert_match expected_body, email.body.encoded
+ assert_match expected_body,
+ @store.read("views/caching_mailer/fragment_cache:#{template_digest("caching_mailer/fragment_cache")}/caching")
+ end
+
+ def test_fragment_caching_in_partials
+ email = @mailer.fragment_cache_in_partials
+ expected_body = "Old fragment caching in a partial"
+ assert_match(expected_body, email.body.encoded)
+
+ assert_match(expected_body,
+ @store.read("views/caching_mailer/_partial:#{template_digest("caching_mailer/_partial")}/caching"))
+ end
+
+ def test_skip_fragment_cache_digesting
+ email = @mailer.skip_fragment_cache_digesting
+ expected_body = "No Digest"
+
+ assert_match expected_body, email.body.encoded
+ assert_match expected_body, @store.read("views/no_digest")
+ end
+
+ def test_fragment_caching_options
+ time = Time.now
+ email = @mailer.fragment_caching_options
+ expected_body = "No Digest"
+
+ assert_match expected_body, email.body.encoded
+ Time.stub(:now, time + 11) do
+ assert_nil @store.read("views/no_digest")
+ end
+ end
+
+ def test_multipart_fragment_caching
+ email = @mailer.multipart_cache
+
+ expected_text_body = "\"Welcome text\""
+ expected_html_body = "\"Welcome html\""
+ encoded_body = email.body.encoded
+ assert_match expected_text_body, encoded_body
+ assert_match expected_html_body, encoded_body
+ assert_match expected_text_body,
+ @store.read("views/text_caching")
+ assert_match expected_html_body,
+ @store.read("views/html_caching")
+ end
+
+ def test_fragment_cache_instrumentation
+ @mailer.enable_fragment_cache_logging = true
+ payload = nil
+
+ subscriber = proc do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ payload = event.payload
+ end
+
+ ActiveSupport::Notifications.subscribed(subscriber, "read_fragment.action_mailer") do
+ @mailer.fragment_cache
+ end
+
+ assert_equal "caching_mailer", payload[:mailer]
+ assert_equal [ :views, "caching_mailer/fragment_cache:#{template_digest("caching_mailer/fragment_cache")}", :caching ], payload[:key]
+ ensure
+ @mailer.enable_fragment_cache_logging = true
+ end
+
+ private
+
+ def template_digest(name)
+ ActionView::Digestor.digest(name: name, finder: @mailer.lookup_context)
+ end
+end
+
+class CacheHelperOutputBufferTest < BaseCachingTest
+ class MockController
+ def read_fragment(name, options)
+ false
+ end
+
+ def write_fragment(name, fragment, options)
+ fragment
+ end
+ end
+
+ def setup
+ super
+ end
+
+ def test_output_buffer
+ output_buffer = ActionView::OutputBuffer.new
+ controller = MockController.new
+ cache_helper = Class.new do
+ def self.controller; end
+ def self.output_buffer; end
+ def self.output_buffer=; end
+ end
+ cache_helper.extend(ActionView::Helpers::CacheHelper)
+
+ cache_helper.stub :controller, controller do
+ cache_helper.stub :output_buffer, output_buffer do
+ assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do
+ assert_nothing_raised do
+ cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new { nil }
+ end
+ end
+ end
+ end
+ end
+
+ def test_safe_buffer
+ output_buffer = ActiveSupport::SafeBuffer.new
+ controller = MockController.new
+ cache_helper = Class.new do
+ def self.controller; end
+ def self.output_buffer; end
+ def self.output_buffer=; end
+ end
+ cache_helper.extend(ActionView::Helpers::CacheHelper)
+
+ cache_helper.stub :controller, controller do
+ cache_helper.stub :output_buffer, output_buffer do
+ assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do
+ assert_nothing_raised do
+ cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new { nil }
+ end
+ end
+ end
+ end
+ end
+end
+
+class ViewCacheDependencyTest < BaseCachingTest
+ class NoDependenciesMailer < ActionMailer::Base
+ end
+ class HasDependenciesMailer < ActionMailer::Base
+ view_cache_dependency { "trombone" }
+ view_cache_dependency { "flute" }
+ end
+
+ def test_view_cache_dependencies_are_empty_by_default
+ assert_empty NoDependenciesMailer.new.view_cache_dependencies
+ end
+
+ def test_view_cache_dependencies_are_listed_in_declaration_order
+ assert_equal %w(trombone flute), HasDependenciesMailer.new.view_cache_dependencies
+ end
+end
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index 2786fe0d07..025f7152bb 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -1,4 +1,6 @@
-require 'abstract_unit'
+# frozen_string_literal: true
+
+require "abstract_unit"
class MyCustomDelivery
end
@@ -23,7 +25,7 @@ class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase
test "default smtp settings" do
settings = { address: "localhost",
port: 25,
- domain: 'localhost.localdomain',
+ domain: "localhost.localdomain",
user_name: nil,
password: nil,
authentication: nil,
@@ -32,14 +34,14 @@ class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase
end
test "default file delivery settings (with Rails.root)" do
- settings = {location: "#{Rails.root}/tmp/mails"}
+ settings = { location: "#{Rails.root}/tmp/mails" }
assert_equal settings, ActionMailer::Base.file_settings
end
test "default sendmail settings" do
settings = {
- location: '/usr/sbin/sendmail',
- arguments: '-i -t'
+ location: "/usr/sbin/sendmail",
+ arguments: "-i"
}
assert_equal settings, ActionMailer::Base.sendmail_settings
end
@@ -83,11 +85,11 @@ end
class MailDeliveryTest < ActiveSupport::TestCase
class DeliveryMailer < ActionMailer::Base
DEFAULT_HEADERS = {
- to: 'mikel@test.lindsaar.net',
- from: 'jose@test.plataformatec.com'
+ to: "mikel@test.lindsaar.net",
+ from: "jose@test.plataformatec.com"
}
- def welcome(hash={})
+ def welcome(hash = {})
mail(DEFAULT_HEADERS.merge(hash))
end
end
@@ -127,16 +129,16 @@ class MailDeliveryTest < ActiveSupport::TestCase
end
test "delivery method options default to class level options" do
- default_options = {a: "b"}
+ default_options = { a: "b" }
ActionMailer::Base.add_delivery_method :optioned, MyOptionedDelivery, default_options
mail_instance = DeliveryMailer.welcome(delivery_method: :optioned)
assert_equal default_options, mail_instance.delivery_method.options
end
test "delivery method options can be overridden per mail instance" do
- default_options = {a: "b"}
+ default_options = { a: "b" }
ActionMailer::Base.add_delivery_method :optioned, MyOptionedDelivery, default_options
- overridden_options = {a: "a"}
+ overridden_options = { a: "a" }
mail_instance = DeliveryMailer.welcome(delivery_method: :optioned, delivery_method_options: overridden_options)
assert_equal overridden_options, mail_instance.delivery_method.options
end
@@ -145,14 +147,14 @@ class MailDeliveryTest < ActiveSupport::TestCase
settings = {
address: "localhost",
port: 25,
- domain: 'localhost.localdomain',
+ domain: "localhost.localdomain",
user_name: nil,
password: nil,
authentication: nil,
enable_starttls_auto: true
}
assert_equal settings, ActionMailer::Base.smtp_settings
- overridden_options = {user_name: "overridden", password: "somethingobtuse"}
+ overridden_options = { user_name: "overridden", password: "somethingobtuse" }
mail_instance = DeliveryMailer.welcome(delivery_method_options: overridden_options)
delivery_method_instance = mail_instance.delivery_method
assert_equal "overridden", delivery_method_instance.settings[:user_name]
diff --git a/actionmailer/test/fixtures/async_mailer/welcome.erb b/actionmailer/test/fixtures/async_mailer/welcome.erb
deleted file mode 100644
index 01f3f00c63..0000000000
--- a/actionmailer/test/fixtures/async_mailer/welcome.erb
+++ /dev/null
@@ -1 +0,0 @@
-Welcome \ No newline at end of file
diff --git a/actionmailer/test/fixtures/caching_mailer/_partial.html.erb b/actionmailer/test/fixtures/caching_mailer/_partial.html.erb
new file mode 100644
index 0000000000..8e965f52b4
--- /dev/null
+++ b/actionmailer/test/fixtures/caching_mailer/_partial.html.erb
@@ -0,0 +1,3 @@
+<% cache :caching do %>
+ Old fragment caching in a partial
+<% end %>
diff --git a/actionmailer/test/fixtures/caching_mailer/fragment_cache.html.erb b/actionmailer/test/fixtures/caching_mailer/fragment_cache.html.erb
new file mode 100644
index 0000000000..90189627da
--- /dev/null
+++ b/actionmailer/test/fixtures/caching_mailer/fragment_cache.html.erb
@@ -0,0 +1,3 @@
+<% cache :caching do %>
+"Welcome"
+<% end %>
diff --git a/actionmailer/test/fixtures/caching_mailer/fragment_cache_in_partials.html.erb b/actionmailer/test/fixtures/caching_mailer/fragment_cache_in_partials.html.erb
new file mode 100644
index 0000000000..2957d083e8
--- /dev/null
+++ b/actionmailer/test/fixtures/caching_mailer/fragment_cache_in_partials.html.erb
@@ -0,0 +1 @@
+<%= render "partial" %>
diff --git a/actionmailer/test/fixtures/caching_mailer/fragment_caching_options.html.erb b/actionmailer/test/fixtures/caching_mailer/fragment_caching_options.html.erb
new file mode 100644
index 0000000000..0541ac321b
--- /dev/null
+++ b/actionmailer/test/fixtures/caching_mailer/fragment_caching_options.html.erb
@@ -0,0 +1,3 @@
+<%= cache :no_digest, skip_digest: true, expires_in: 0 do %>
+ No Digest
+<% end %>
diff --git a/actionmailer/test/fixtures/caching_mailer/multipart_cache.html.erb b/actionmailer/test/fixtures/caching_mailer/multipart_cache.html.erb
new file mode 100644
index 0000000000..0d26baa2d7
--- /dev/null
+++ b/actionmailer/test/fixtures/caching_mailer/multipart_cache.html.erb
@@ -0,0 +1,3 @@
+<% cache :html_caching, skip_digest: true do %>
+ "Welcome html"
+<% end %>
diff --git a/actionmailer/test/fixtures/caching_mailer/multipart_cache.text.erb b/actionmailer/test/fixtures/caching_mailer/multipart_cache.text.erb
new file mode 100644
index 0000000000..ef97326e03
--- /dev/null
+++ b/actionmailer/test/fixtures/caching_mailer/multipart_cache.text.erb
@@ -0,0 +1,3 @@
+<% cache :text_caching, skip_digest: true do %>
+ "Welcome text"
+<% end %>
diff --git a/actionmailer/test/fixtures/caching_mailer/skip_fragment_cache_digesting.html.erb b/actionmailer/test/fixtures/caching_mailer/skip_fragment_cache_digesting.html.erb
new file mode 100644
index 0000000000..0d52429a81
--- /dev/null
+++ b/actionmailer/test/fixtures/caching_mailer/skip_fragment_cache_digesting.html.erb
@@ -0,0 +1,3 @@
+<%= cache :no_digest, skip_digest: true do %>
+ No Digest
+<% end %>
diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb
index 6124ffeb52..6e75cff347 100644
--- a/actionmailer/test/i18n_with_controller_test.rb
+++ b/actionmailer/test/i18n_with_controller_test.rb
@@ -1,10 +1,12 @@
-require 'abstract_unit'
-require 'action_view'
-require 'action_controller'
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "action_view"
+require "action_controller"
class I18nTestMailer < ActionMailer::Base
configure do |c|
- c.assets_dir = ''
+ c.assets_dir = ""
end
def mail_with_i18n_subject(recipient)
@@ -18,14 +20,16 @@ end
class TestController < ActionController::Base
def send_mail
email = I18nTestMailer.mail_with_i18n_subject("test@localhost").deliver_now
- render text: "Mail sent - Subject: #{email.subject}"
+ render plain: "Mail sent - Subject: #{email.subject}"
end
end
class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new
Routes.draw do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ":controller(/:action(/:id))"
+ end
end
class RoutedRackApp
@@ -54,22 +58,20 @@ class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest
def test_send_mail
stub_any_instance(Mail::SMTP, instance: Mail::SMTP.new({})) do |instance|
assert_called(instance, :deliver!) do
- with_translation 'de', email_subject: '[Anmeldung] Willkommen' do
- ActiveSupport::Deprecation.silence do
- get '/test/send_mail'
- end
+ with_translation "de", email_subject: "[Anmeldung] Willkommen" do
+ get "/test/send_mail"
assert_equal "Mail sent - Subject: [Anmeldung] Willkommen", @response.body
end
end
end
end
- protected
+ private
- def with_translation(locale, data)
- I18n.backend.store_translations(locale, data)
- yield
- ensure
- I18n.backend.reload!
- end
+ def with_translation(locale, data)
+ I18n.backend.store_translations(locale, data)
+ yield
+ ensure
+ I18n.backend.reload!
+ end
end
diff --git a/actionmailer/test/log_subscriber_test.rb b/actionmailer/test/log_subscriber_test.rb
index 3871b16840..7686fd10c9 100644
--- a/actionmailer/test/log_subscriber_test.rb
+++ b/actionmailer/test/log_subscriber_test.rb
@@ -1,7 +1,9 @@
-require 'abstract_unit'
-require 'mailers/base_mailer'
-require 'active_support/log_subscriber/test_helper'
-require 'action_mailer/log_subscriber'
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "mailers/base_mailer"
+require "active_support/log_subscriber/test_helper"
+require "action_mailer/log_subscriber"
class AMLogSubscriberTest < ActionMailer::TestCase
include ActiveSupport::LogSubscriber::TestHelper
@@ -26,7 +28,7 @@ class AMLogSubscriberTest < ActionMailer::TestCase
wait
assert_equal(1, @logger.logged(:info).size)
- assert_match(/Sent mail to system@test.lindsaar.net/, @logger.logged(:info).first)
+ assert_match(/Sent mail to system@test\.lindsaar\.net/, @logger.logged(:info).first)
assert_equal(2, @logger.logged(:debug).size)
assert_match(/BaseMailer#welcome: processed outbound mail in [\d.]+ms/, @logger.logged(:debug).first)
@@ -35,8 +37,22 @@ class AMLogSubscriberTest < ActionMailer::TestCase
BaseMailer.deliveries.clear
end
+ def test_deliver_message_when_perform_deliveries_is_false
+ BaseMailer.welcome_without_deliveries.deliver_now
+ wait
+
+ assert_equal(1, @logger.logged(:info).size)
+ assert_match("Skipped sending mail to system@test.lindsaar.net as `perform_deliveries` is false", @logger.logged(:info).first)
+
+ assert_equal(2, @logger.logged(:debug).size)
+ assert_match(/BaseMailer#welcome_without_deliveries: processed outbound mail in [\d.]+ms/, @logger.logged(:debug).first)
+ assert_match("Welcome", @logger.logged(:debug).second)
+ ensure
+ BaseMailer.deliveries.clear
+ end
+
def test_receive_is_notified
- fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email")
+ fixture = File.read(File.expand_path("fixtures/raw_email", __dir__))
TestMailer.receive(fixture)
wait
assert_equal(1, @logger.logged(:info).size)
diff --git a/actionmailer/test/mail_helper_test.rb b/actionmailer/test/mail_helper_test.rb
index ff6b25b0c7..51d6ccb10f 100644
--- a/actionmailer/test/mail_helper_test.rb
+++ b/actionmailer/test/mail_helper_test.rb
@@ -1,12 +1,14 @@
-require 'abstract_unit'
+# frozen_string_literal: true
+
+require "abstract_unit"
class HelperMailer < ActionMailer::Base
def use_mail_helper
- @text = "But soft! What light through yonder window breaks? It is the east, " +
- "and Juliet is the sun. Arise, fair sun, and kill the envious moon, " +
- "which is sick and pale with grief that thou, her maid, art far more " +
- "fair than she. Be not her maid, for she is envious! Her vestal " +
- "livery is but sick and green, and none but fools do wear it. Cast " +
+ @text = "But soft! What light through yonder window breaks? It is the east, " \
+ "and Juliet is the sun. Arise, fair sun, and kill the envious moon, " \
+ "which is sick and pale with grief that thou, her maid, art far more " \
+ "fair than she. Be not her maid, for she is envious! Her vestal " \
+ "livery is but sick and green, and none but fools do wear it. Cast " \
"it off!"
mail_with_defaults do |format|
@@ -65,12 +67,12 @@ The second
end
end
- protected
+ private
- def mail_with_defaults(&block)
- mail(to: "test@localhost", from: "tester@example.com",
- subject: "using helpers", &block)
- end
+ def mail_with_defaults(&block)
+ mail(to: "test@localhost", from: "tester@example.com",
+ subject: "using helpers", &block)
+ end
end
class MailerHelperTest < ActionMailer::TestCase
diff --git a/actionmailer/test/mail_layout_test.rb b/actionmailer/test/mail_layout_test.rb
index 166dd096d4..16d77ed61d 100644
--- a/actionmailer/test/mail_layout_test.rb
+++ b/actionmailer/test/mail_layout_test.rb
@@ -1,7 +1,9 @@
-require 'abstract_unit'
+# frozen_string_literal: true
+
+require "abstract_unit"
class AutoLayoutMailer < ActionMailer::Base
- default to: 'test@localhost',
+ default to: "test@localhost",
subject: "You have a mail",
from: "tester@example.com"
@@ -11,7 +13,7 @@ class AutoLayoutMailer < ActionMailer::Base
def spam
@world = "Earth"
- mail(body: render(inline: "Hello, <%= @world %>", layout: 'spam'))
+ mail(body: render(inline: "Hello, <%= @world %>", layout: "spam"))
end
def nolayout
@@ -28,9 +30,9 @@ class AutoLayoutMailer < ActionMailer::Base
end
class ExplicitLayoutMailer < ActionMailer::Base
- layout 'spam', except: [:logout]
+ layout "spam", except: [:logout]
- default to: 'test@localhost',
+ default to: "test@localhost",
subject: "You have a mail",
from: "tester@example.com"
@@ -54,10 +56,10 @@ class LayoutMailerTest < ActiveSupport::TestCase
assert_equal "multipart/alternative", mail.mime_type
assert_equal 2, mail.parts.size
- assert_equal 'text/plain', mail.parts.first.mime_type
+ assert_equal "text/plain", mail.parts.first.mime_type
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s
- assert_equal 'text/html', mail.parts.last.mime_type
+ assert_equal "text/html", mail.parts.last.mime_type
assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s
end
@@ -66,10 +68,10 @@ class LayoutMailerTest < ActiveSupport::TestCase
assert_equal "multipart/mixed", mail.mime_type
assert_equal 2, mail.parts.size
- assert_equal 'text/plain', mail.parts.first.mime_type
+ assert_equal "text/plain", mail.parts.first.mime_type
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s
- assert_equal 'text/html', mail.parts.last.mime_type
+ assert_equal "text/html", mail.parts.last.mime_type
assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s
end
diff --git a/actionmailer/test/mailers/asset_mailer.rb b/actionmailer/test/mailers/asset_mailer.rb
index f54a50d00d..7a9aba2629 100644
--- a/actionmailer/test/mailers/asset_mailer.rb
+++ b/actionmailer/test/mailers/asset_mailer.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: true
+
class AssetMailer < ActionMailer::Base
self.mailer_name = "asset_mailer"
def welcome
- mail
+ mail
end
end
diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb
index f75bd48a9e..c1bb48cc96 100644
--- a/actionmailer/test/mailers/base_mailer.rb
+++ b/actionmailer/test/mailers/base_mailer.rb
@@ -1,13 +1,15 @@
+# frozen_string_literal: true
+
class BaseMailer < ActionMailer::Base
self.mailer_name = "base_mailer"
- default to: 'system@test.lindsaar.net',
- from: 'jose@test.plataformatec.com',
- reply_to: 'mikel@test.lindsaar.net'
+ default to: "system@test.lindsaar.net",
+ from: "jose@test.plataformatec.com",
+ reply_to: "mikel@test.lindsaar.net"
def welcome(hash = {})
- headers['X-SPAM'] = "Not SPAM"
- mail({subject: "The first email on new API!"}.merge!(hash))
+ headers["X-SPAM"] = "Not SPAM"
+ mail({ subject: "The first email on new API!" }.merge!(hash))
end
def welcome_with_headers(hash = {})
@@ -19,6 +21,11 @@ class BaseMailer < ActionMailer::Base
mail(template_name: "welcome", template_path: path)
end
+ def welcome_without_deliveries
+ mail(template_name: "welcome")
+ mail.perform_deliveries = false
+ end
+
def html_only(hash = {})
mail(hash)
end
@@ -28,30 +35,30 @@ class BaseMailer < ActionMailer::Base
end
def inline_attachment
- attachments.inline['logo.png'] = "\312\213\254\232"
+ attachments.inline["logo.png"] = "\312\213\254\232"
mail
end
def attachment_with_content(hash = {})
- attachments['invoice.pdf'] = 'This is test File content'
+ attachments["invoice.pdf"] = "This is test File content"
mail(hash)
end
def attachment_with_hash
- attachments['invoice.jpg'] = { data: ::Base64.encode64("\312\213\254\232)b"),
+ attachments["invoice.jpg"] = { data: ::Base64.encode64("\312\213\254\232)b"),
mime_type: "image/x-jpg",
transfer_encoding: "base64" }
mail
end
def attachment_with_hash_default_encoding
- attachments['invoice.jpg'] = { data: "\312\213\254\232)b",
+ attachments["invoice.jpg"] = { data: "\312\213\254\232)b",
mime_type: "image/x-jpg" }
mail
end
def implicit_multipart(hash = {})
- attachments['invoice.pdf'] = 'This is test File content' if hash.delete(:attachments)
+ attachments["invoice.pdf"] = "This is test File content" if hash.delete(:attachments)
mail(hash)
end
@@ -60,10 +67,10 @@ class BaseMailer < ActionMailer::Base
end
def explicit_multipart(hash = {})
- attachments['invoice.pdf'] = 'This is test File content' if hash.delete(:attachments)
+ attachments["invoice.pdf"] = "This is test File content" if hash.delete(:attachments)
mail(hash) do |format|
- format.text { render text: "TEXT Explicit Multipart" }
- format.html { render text: "HTML Explicit Multipart" }
+ format.text { render plain: "TEXT Explicit Multipart" }
+ format.html { render plain: "HTML Explicit Multipart" }
end
end
@@ -76,7 +83,7 @@ class BaseMailer < ActionMailer::Base
def explicit_multipart_with_any(hash = {})
mail(hash) do |format|
- format.any(:text, :html){ render text: "Format with any!" }
+ format.any(:text, :html) { render plain: "Format with any!" }
end
end
@@ -88,8 +95,8 @@ class BaseMailer < ActionMailer::Base
def explicit_multipart_with_options(include_html = false)
mail do |format|
- format.text(content_transfer_encoding: "base64"){ render "welcome" }
- format.html{ render "welcome" } if include_html
+ format.text(content_transfer_encoding: "base64") { render "welcome" }
+ format.html { render "welcome" } if include_html
end
end
@@ -100,25 +107,25 @@ class BaseMailer < ActionMailer::Base
end
end
- def implicit_different_template(template_name='')
+ def implicit_different_template(template_name = "")
mail(template_name: template_name)
end
- def implicit_different_template_with_block(template_name='')
+ def implicit_different_template_with_block(template_name = "")
mail(template_name: template_name) do |format|
format.text
format.html
end
end
- def explicit_different_template(template_name='')
+ def explicit_different_template(template_name = "")
mail do |format|
format.text { render template: "#{mailer_name}/#{template_name}" }
format.html { render template: "#{mailer_name}/#{template_name}" }
end
end
- def different_layout(layout_name='')
+ def different_layout(layout_name = "")
mail do |format|
format.text { render layout: layout_name }
format.html { render layout: layout_name }
@@ -138,6 +145,6 @@ class BaseMailer < ActionMailer::Base
end
def with_subject_interpolations
- mail(subject: default_i18n_subject(rapper_or_impersonator: 'Slim Shady'), body: '')
+ mail(subject: default_i18n_subject(rapper_or_impersonator: "Slim Shady"), body: "")
end
end
diff --git a/actionmailer/test/mailers/caching_mailer.rb b/actionmailer/test/mailers/caching_mailer.rb
new file mode 100644
index 0000000000..02f0c6c103
--- /dev/null
+++ b/actionmailer/test/mailers/caching_mailer.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class CachingMailer < ActionMailer::Base
+ self.mailer_name = "caching_mailer"
+
+ def fragment_cache
+ mail(subject: "welcome", template_name: "fragment_cache")
+ end
+
+ def fragment_cache_in_partials
+ mail(subject: "welcome", template_name: "fragment_cache_in_partials")
+ end
+
+ def skip_fragment_cache_digesting
+ mail(subject: "welcome", template_name: "skip_fragment_cache_digesting")
+ end
+
+ def fragment_caching_options
+ mail(subject: "welcome", template_name: "fragment_caching_options")
+ end
+
+ def multipart_cache
+ mail(subject: "welcome", template_name: "multipart_cache")
+ end
+end
diff --git a/actionmailer/test/mailers/delayed_mailer.rb b/actionmailer/test/mailers/delayed_mailer.rb
index 62d4baa434..b0f5ecc2fb 100644
--- a/actionmailer/test/mailers/delayed_mailer.rb
+++ b/actionmailer/test/mailers/delayed_mailer.rb
@@ -1,6 +1,28 @@
+# frozen_string_literal: true
+
+require "active_job/arguments"
+
+class DelayedMailerError < StandardError; end
+
class DelayedMailer < ActionMailer::Base
+ cattr_accessor :last_error
+ cattr_accessor :last_rescue_from_instance
+
+ rescue_from DelayedMailerError do |error|
+ @@last_error = error
+ @@last_rescue_from_instance = self
+ end
+
+ rescue_from ActiveJob::DeserializationError do |error|
+ @@last_error = error
+ @@last_rescue_from_instance = self
+ end
def test_message(*)
- mail(from: 'test-sender@test.com', to: 'test-receiver@test.com', subject: 'Test Subject', body: 'Test Body')
+ mail(from: "test-sender@test.com", to: "test-receiver@test.com", subject: "Test Subject", body: "Test Body")
+ end
+
+ def test_raise(klass_name)
+ raise klass_name.constantize, "boom"
end
end
diff --git a/actionmailer/test/mailers/params_mailer.rb b/actionmailer/test/mailers/params_mailer.rb
new file mode 100644
index 0000000000..84aa336311
--- /dev/null
+++ b/actionmailer/test/mailers/params_mailer.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ParamsMailer < ActionMailer::Base
+ before_action { @inviter, @invitee = params[:inviter], params[:invitee] }
+
+ default to: Proc.new { @invitee }, from: -> { @inviter }
+
+ def invitation
+ mail(subject: "Welcome to the project!") do |format|
+ format.text { render plain: "So says #{@inviter}" }
+ end
+ end
+end
diff --git a/actionmailer/test/mailers/proc_mailer.rb b/actionmailer/test/mailers/proc_mailer.rb
index 7e189d861f..76e730bb79 100644
--- a/actionmailer/test/mailers/proc_mailer.rb
+++ b/actionmailer/test/mailers/proc_mailer.rb
@@ -1,17 +1,25 @@
+# frozen_string_literal: true
+
class ProcMailer < ActionMailer::Base
- default to: 'system@test.lindsaar.net',
- 'X-Proc-Method' => Proc.new { Time.now.to_i.to_s },
+ default to: "system@test.lindsaar.net",
+ "X-Proc-Method" => Proc.new { Time.now.to_i.to_s },
subject: Proc.new { give_a_greeting },
- 'x-has-to-proc' => :symbol
+ "x-has-to-proc" => :symbol,
+ "X-Lambda-Arity-0" => ->() { "0" },
+ "X-Lambda-Arity-1-arg" => ->(arg) { arg.computed_value },
+ "X-Lambda-Arity-1-self" => ->(_) { self.computed_value }
def welcome
mail
end
- private
-
- def give_a_greeting
- "Thanks for signing up this afternoon"
+ def computed_value
+ "complex_value"
end
+ private
+
+ def give_a_greeting
+ "Thanks for signing up this afternoon"
+ end
end
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index b834cdd08c..f8dcb3f4ba 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -1,6 +1,8 @@
-require 'abstract_unit'
-require 'active_job'
-require 'mailers/delayed_mailer'
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_job"
+require "mailers/delayed_mailer"
class MessageDeliveryTest < ActiveSupport::TestCase
include ActiveJob::TestHelper
@@ -12,85 +14,154 @@ class MessageDeliveryTest < ActiveSupport::TestCase
ActionMailer::Base.deliver_later_queue_name = :test_queue
ActionMailer::Base.delivery_method = :test
ActiveJob::Base.logger = Logger.new(nil)
- @mail = DelayedMailer.test_message(1, 2, 3)
- ActionMailer::Base.deliveries.clear
ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
+
+ DelayedMailer.last_error = nil
+ DelayedMailer.last_rescue_from_instance = nil
+
+ @mail = DelayedMailer.test_message(1, 2, 3)
end
teardown do
+ ActionMailer::Base.deliveries.clear
+
ActiveJob::Base.logger = @previous_logger
ActionMailer::Base.delivery_method = @previous_delivery_method
ActionMailer::Base.deliver_later_queue_name = @previous_deliver_later_queue_name
+
+ DelayedMailer.last_error = nil
+ DelayedMailer.last_rescue_from_instance = nil
end
- test 'should have a message' do
+ test "should have a message" do
assert @mail.message
end
- test 'its message should be a Mail::Message' do
- assert_equal Mail::Message , @mail.message.class
+ test "its message should be a Mail::Message" do
+ assert_equal Mail::Message, @mail.message.class
end
- test 'should respond to .deliver_later' do
+ test "should respond to .deliver_later" do
assert_respond_to @mail, :deliver_later
end
- test 'should respond to .deliver_later!' do
+ test "should respond to .deliver_later!" do
assert_respond_to @mail, :deliver_later!
end
- test 'should respond to .deliver_now' do
+ test "should respond to .deliver_now" do
assert_respond_to @mail, :deliver_now
end
- test 'should respond to .deliver_now!' do
+ test "should respond to .deliver_now!" do
assert_respond_to @mail, :deliver_now!
end
def test_should_enqueue_and_run_correctly_in_activejob
@mail.deliver_later!
assert_equal 1, ActionMailer::Base.deliveries.size
- ensure
- ActionMailer::Base.deliveries.clear
end
- test 'should enqueue the email with :deliver_now delivery method' do
- assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do
+ test "should enqueue the email with :deliver_now delivery method" do
+ assert_performed_with(job: ActionMailer::DeliveryJob, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
@mail.deliver_later
end
end
- test 'should enqueue the email with :deliver_now! delivery method' do
- assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now!', 1, 2, 3]) do
+ test "should enqueue the email with :deliver_now! delivery method" do
+ assert_performed_with(job: ActionMailer::DeliveryJob, args: ["DelayedMailer", "test_message", "deliver_now!", 1, 2, 3]) do
@mail.deliver_later!
end
end
- test 'should enqueue a delivery with a delay' do
+ test "should enqueue a delivery with a delay" do
travel_to Time.new(2004, 11, 24, 01, 04, 44) do
- assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current.to_f+600.seconds, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do
- @mail.deliver_later wait: 600.seconds
+ assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current + 10.minutes, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
+ @mail.deliver_later wait: 10.minutes
end
end
end
- test 'should enqueue a delivery at a specific time' do
- later_time = Time.now.to_f + 3600
- assert_performed_with(job: ActionMailer::DeliveryJob, at: later_time, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do
+ test "should enqueue a delivery at a specific time" do
+ later_time = Time.current + 1.hour
+ assert_performed_with(job: ActionMailer::DeliveryJob, at: later_time, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
@mail.deliver_later wait_until: later_time
end
end
- test 'should enqueue the job on the correct queue' do
- assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], queue: "test_queue") do
+ test "should enqueue the job on the correct queue" do
+ assert_performed_with(job: ActionMailer::DeliveryJob, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3], queue: "test_queue") do
@mail.deliver_later
end
end
- test 'can override the queue when enqueuing mail' do
- assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], queue: "another_queue") do
+ test "should enqueue the job with the correct delivery job" do
+ old_delivery_job = DelayedMailer.delivery_job
+ DelayedMailer.delivery_job = DummyJob
+
+ assert_performed_with(job: DummyJob, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
+ @mail.deliver_later
+ end
+
+ DelayedMailer.delivery_job = old_delivery_job
+ end
+
+ class DummyJob < ActionMailer::DeliveryJob; end
+
+ test "can override the queue when enqueuing mail" do
+ assert_performed_with(job: ActionMailer::DeliveryJob, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3], queue: "another_queue") do
@mail.deliver_later(queue: :another_queue)
end
end
+
+ test "deliver_later after accessing the message is disallowed" do
+ @mail.message # Load the message, which calls the mailer method.
+
+ assert_raise RuntimeError do
+ @mail.deliver_later
+ end
+ end
+
+ test "job delegates error handling to mailer" do
+ # Superclass not rescued by mailer's rescue_from RuntimeError
+ message = DelayedMailer.test_raise("StandardError")
+ assert_raise(StandardError) { message.deliver_later }
+ assert_nil DelayedMailer.last_error
+ assert_nil DelayedMailer.last_rescue_from_instance
+
+ # Rescued by mailer's rescue_from RuntimeError
+ message = DelayedMailer.test_raise("DelayedMailerError")
+ assert_nothing_raised { message.deliver_later }
+ assert_equal "boom", DelayedMailer.last_error.message
+ assert_kind_of DelayedMailer, DelayedMailer.last_rescue_from_instance
+ end
+
+ class DeserializationErrorFixture
+ include GlobalID::Identification
+
+ def self.find(id)
+ raise "boom, missing find"
+ end
+
+ attr_reader :id
+ def initialize(id = 1)
+ @id = id
+ end
+
+ def to_global_id(options = {})
+ super app: "foo"
+ end
+ end
+
+ test "job delegates deserialization errors to mailer class" do
+ # Inject an argument that can't be deserialized.
+ message = DelayedMailer.test_message(DeserializationErrorFixture.new)
+
+ # DeserializationError is raised, rescued, and delegated to the handler
+ # on the mailer class.
+ assert_nothing_raised { message.deliver_later }
+ assert_equal DelayedMailer, DelayedMailer.last_rescue_from_instance
+ assert_equal "Error while trying to deserialize arguments: boom, missing find", DelayedMailer.last_error.message
+ end
end
diff --git a/actionmailer/test/parameterized_test.rb b/actionmailer/test/parameterized_test.rb
new file mode 100644
index 0000000000..d3b34fc3e3
--- /dev/null
+++ b/actionmailer/test/parameterized_test.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_job"
+require "mailers/params_mailer"
+
+class ParameterizedTest < ActiveSupport::TestCase
+ include ActiveJob::TestHelper
+
+ setup do
+ @previous_logger = ActiveJob::Base.logger
+ ActiveJob::Base.logger = Logger.new(nil)
+
+ @previous_delivery_method = ActionMailer::Base.delivery_method
+ ActionMailer::Base.delivery_method = :test
+
+ @previous_deliver_later_queue_name = ActionMailer::Base.deliver_later_queue_name
+ ActionMailer::Base.deliver_later_queue_name = :test_queue
+
+ @mail = ParamsMailer.with(inviter: "david@basecamp.com", invitee: "jason@basecamp.com").invitation
+ end
+
+ teardown do
+ ActiveJob::Base.logger = @previous_logger
+ ParamsMailer.deliveries.clear
+
+ ActionMailer::Base.delivery_method = @previous_delivery_method
+ ActionMailer::Base.deliver_later_queue_name = @previous_deliver_later_queue_name
+ end
+
+ test "parameterized headers" do
+ assert_equal(["jason@basecamp.com"], @mail.to)
+ assert_equal(["david@basecamp.com"], @mail.from)
+ assert_equal("So says david@basecamp.com", @mail.body.encoded)
+ end
+
+ test "enqueue the email with params" do
+ assert_performed_with(job: ActionMailer::Parameterized::DeliveryJob, args: ["ParamsMailer", "invitation", "deliver_now", { inviter: "david@basecamp.com", invitee: "jason@basecamp.com" } ]) do
+ @mail.deliver_later
+ end
+ end
+
+ test "respond_to?" do
+ mailer = ParamsMailer.with(inviter: "david@basecamp.com", invitee: "jason@basecamp.com")
+
+ assert_respond_to mailer, :invitation
+ assert_not_respond_to mailer, :anything
+
+ invitation = mailer.method(:invitation)
+ assert_equal Method, invitation.class
+
+ assert_raises(NameError) do
+ invitation = mailer.method(:anything)
+ end
+ end
+
+ test "should enqueue a parameterized request with the correct delivery job" do
+ old_delivery_job = ParamsMailer.parameterized_delivery_job
+ ParamsMailer.parameterized_delivery_job = ParameterizedDummyJob
+
+ assert_performed_with(job: ParameterizedDummyJob, args: ["ParamsMailer", "invitation", "deliver_now", { inviter: "david@basecamp.com", invitee: "jason@basecamp.com" } ]) do
+ @mail.deliver_later
+ end
+
+ ParamsMailer.parameterized_delivery_job = old_delivery_job
+ end
+
+ class ParameterizedDummyJob < ActionMailer::Parameterized::DeliveryJob; end
+end
diff --git a/actionmailer/test/test_case_test.rb b/actionmailer/test/test_case_test.rb
index 86fd37bea6..7b9647d295 100644
--- a/actionmailer/test/test_case_test.rb
+++ b/actionmailer/test/test_case_test.rb
@@ -1,8 +1,48 @@
-require 'abstract_unit'
+# frozen_string_literal: true
+
+require "abstract_unit"
class TestTestMailer < ActionMailer::Base
end
+class ClearTestDeliveriesMixinTest < ActiveSupport::TestCase
+ include ActionMailer::TestCase::ClearTestDeliveries
+
+ def before_setup
+ ActionMailer::Base.delivery_method, @original_delivery_method = :test, ActionMailer::Base.delivery_method
+ ActionMailer::Base.deliveries << "better clear me, setup"
+ super
+ end
+
+ def after_teardown
+ super
+ assert_equal [], ActionMailer::Base.deliveries
+ ActionMailer::Base.delivery_method = @original_delivery_method
+ end
+
+ def test_deliveries_are_cleared_on_setup_and_teardown
+ assert_equal [], ActionMailer::Base.deliveries
+ ActionMailer::Base.deliveries << "better clear me, teardown"
+ end
+end
+
+class MailerDeliveriesClearingTest < ActionMailer::TestCase
+ def before_setup
+ ActionMailer::Base.deliveries << "better clear me, setup"
+ super
+ end
+
+ def after_teardown
+ super
+ assert_equal [], ActionMailer::Base.deliveries
+ end
+
+ def test_deliveries_are_cleared_on_setup_and_teardown
+ assert_equal [], ActionMailer::Base.deliveries
+ ActionMailer::Base.deliveries << "better clear me, teardown"
+ end
+end
+
class CrazyNameMailerTest < ActionMailer::TestCase
tests TestTestMailer
@@ -20,7 +60,7 @@ class CrazySymbolNameMailerTest < ActionMailer::TestCase
end
class CrazyStringNameMailerTest < ActionMailer::TestCase
- tests 'test_test_mailer'
+ tests "test_test_mailer"
def test_set_mailer_class_manual_using_string
assert_equal TestTestMailer, self.class.mailer_class
diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb
index 0a4bc75d3e..d31170706b 100644
--- a/actionmailer/test/test_helper_test.rb
+++ b/actionmailer/test/test_helper_test.rb
@@ -1,5 +1,7 @@
-require 'abstract_unit'
-require 'active_support/testing/stream'
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/testing/stream"
class TestHelperMailer < ActionMailer::Base
def test
@@ -8,6 +10,18 @@ class TestHelperMailer < ActionMailer::Base
to: "test@example.com",
from: "tester@example.com"
end
+
+ def test_args(recipient, name)
+ mail body: render(inline: "Hello, #{name}"),
+ to: recipient,
+ from: "tester@example.com"
+ end
+
+ def test_parameter_args
+ mail body: render(inline: "All is #{params[:all]}"),
+ to: "test@example.com",
+ from: "tester@example.com"
+ end
end
class TestHelperMailerTest < ActionMailer::TestCase
@@ -40,11 +54,11 @@ class TestHelperMailerTest < ActionMailer::TestCase
end
def test_encode
- assert_equal '=?UTF-8?Q?This_is_=E3=81=82_string?=', encode('This is あ string')
+ assert_equal "This is あ string", Mail::Encodings.q_value_decode(encode("This is あ string"))
end
def test_read_fixture
- assert_equal ['Welcome!'], read_fixture('welcome')
+ assert_equal ["Welcome!"], read_fixture("welcome")
end
def test_assert_emails
@@ -55,6 +69,16 @@ class TestHelperMailerTest < ActionMailer::TestCase
end
end
+ def test_assert_emails_with_enqueued_emails
+ assert_nothing_raised do
+ assert_emails 1 do
+ silence_stream($stdout) do
+ TestHelperMailer.test.deliver_later
+ end
+ end
+ end
+ end
+
def test_repeated_assert_emails_calls
assert_nothing_raised do
assert_emails 1 do
@@ -91,6 +115,18 @@ class TestHelperMailerTest < ActionMailer::TestCase
end
end
+ def test_assert_no_emails_with_enqueued_emails
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_no_emails do
+ silence_stream($stdout) do
+ TestHelperMailer.test.deliver_later
+ end
+ end
+ end
+
+ assert_match(/0 .* but 1/, error.message)
+ end
+
def test_assert_emails_too_few_sent
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_emails 2 do
@@ -143,6 +179,16 @@ class TestHelperMailerTest < ActionMailer::TestCase
end
end
+ def test_assert_enqueued_parameterized_emails
+ assert_nothing_raised do
+ assert_enqueued_emails 1 do
+ silence_stream($stdout) do
+ TestHelperMailer.with(a: 1).test.deliver_later
+ end
+ end
+ end
+ end
+
def test_assert_enqueued_emails_too_few_sent
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_enqueued_emails 2 do
@@ -176,6 +222,14 @@ class TestHelperMailerTest < ActionMailer::TestCase
end
end
+ def test_assert_no_enqueued_parameterized_emails
+ assert_nothing_raised do
+ assert_no_enqueued_emails do
+ TestHelperMailer.with(a: 1).test.deliver_now
+ end
+ end
+ end
+
def test_assert_no_enqueued_emails_failure
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_no_enqueued_emails do
@@ -187,6 +241,63 @@ class TestHelperMailerTest < ActionMailer::TestCase
assert_match(/0 .* but 1/, error.message)
end
+
+ def test_assert_enqueued_email_with
+ assert_nothing_raised do
+ assert_enqueued_email_with TestHelperMailer, :test do
+ silence_stream($stdout) do
+ TestHelperMailer.test.deliver_later
+ end
+ end
+ end
+ end
+
+ def test_assert_enqueued_email_with_with_no_block
+ assert_nothing_raised do
+ silence_stream($stdout) do
+ TestHelperMailer.test.deliver_later
+ assert_enqueued_email_with TestHelperMailer, :test
+ end
+ end
+ end
+
+ def test_assert_enqueued_email_with_with_args
+ assert_nothing_raised do
+ assert_enqueued_email_with TestHelperMailer, :test_args, args: ["some_email", "some_name"] do
+ silence_stream($stdout) do
+ TestHelperMailer.test_args("some_email", "some_name").deliver_later
+ end
+ end
+ end
+ end
+
+ def test_assert_enqueued_email_with_with_no_block_with_args
+ assert_nothing_raised do
+ silence_stream($stdout) do
+ TestHelperMailer.test_args("some_email", "some_name").deliver_later
+ assert_enqueued_email_with TestHelperMailer, :test_args, args: ["some_email", "some_name"]
+ end
+ end
+ end
+
+ def test_assert_enqueued_email_with_with_parameterized_args
+ assert_nothing_raised do
+ assert_enqueued_email_with TestHelperMailer, :test_parameter_args, args: { all: "good" } do
+ silence_stream($stdout) do
+ TestHelperMailer.with(all: "good").test_parameter_args.deliver_later
+ end
+ end
+ end
+ end
+
+ def test_assert_enqueued_email_with_with_no_block_with_parameterized_args
+ assert_nothing_raised do
+ silence_stream($stdout) do
+ TestHelperMailer.with(all: "good").test_parameter_args.deliver_later
+ assert_enqueued_email_with TestHelperMailer, :test_parameter_args, args: { all: "good" }
+ end
+ end
+ end
end
class AnotherTestHelperMailerTest < ActionMailer::TestCase
@@ -198,6 +309,6 @@ class AnotherTestHelperMailerTest < ActionMailer::TestCase
def test_setup_shouldnt_conflict_with_mailer_setup
assert_kind_of Mail::Message, @expected
- assert_equal 'a value', @test_var
+ assert_equal "a value", @test_var
end
end
diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb
index 7928fe9542..a926663a9f 100644
--- a/actionmailer/test/url_test.rb
+++ b/actionmailer/test/url_test.rb
@@ -1,20 +1,27 @@
-require 'abstract_unit'
-require 'action_controller'
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "action_controller"
class WelcomeController < ActionController::Base
end
AppRoutes = ActionDispatch::Routing::RouteSet.new
-class ActionMailer::Base
- include AppRoutes.url_helpers
+AppRoutes.draw do
+ get "/welcome" => "foo#bar", as: "welcome"
+ get "/dummy_model" => "foo#baz", as: "dummy_model"
+ get "/welcome/greeting", to: "welcome#greeting"
+ get "/a/b(/:id)", to: "a#b"
end
class UrlTestMailer < ActionMailer::Base
- default_url_options[:host] = 'www.basecamphq.com'
+ include AppRoutes.url_helpers
+
+ default_url_options[:host] = "www.basecamphq.com"
configure do |c|
- c.assets_dir = '' # To get the tests to pass
+ c.assets_dir = "" # To get the tests to pass
end
def signed_up_with_url(recipient)
@@ -27,14 +34,14 @@ class UrlTestMailer < ActionMailer::Base
def exercise_url_for(options)
@options = options
@url = url_for(@options)
- mail(from: 'from@example.com', to: 'to@example.com', subject: 'subject')
+ mail(from: "from@example.com", to: "to@example.com", subject: "subject")
end
end
class ActionMailerUrlTest < ActionMailer::TestCase
class DummyModel
def self.model_name
- OpenStruct.new(route_key: 'dummy_model')
+ OpenStruct.new(route_key: "dummy_model")
end
def persisted?
@@ -50,11 +57,11 @@ class ActionMailerUrlTest < ActionMailer::TestCase
end
end
- def encode( text, charset="UTF-8" )
- quoted_printable( text, charset )
+ def encode(text, charset = "UTF-8")
+ quoted_printable(text, charset)
end
- def new_mail( charset="UTF-8" )
+ def new_mail(charset = "UTF-8")
mail = Mail.new
mail.mime_version = "1.0"
if charset
@@ -64,7 +71,7 @@ class ActionMailerUrlTest < ActionMailer::TestCase
end
def assert_url_for(expected, options, relative = false)
- expected = "http://www.basecamphq.com#{expected}" if expected.start_with?('/') && !relative
+ expected = "http://www.basecamphq.com#{expected}" if expected.start_with?("/") && !relative
urls = UrlTestMailer.exercise_url_for(options).body.to_s.chomp.split
assert_equal expected, urls.first
@@ -72,50 +79,39 @@ class ActionMailerUrlTest < ActionMailer::TestCase
end
def setup
- @recipient = 'test@localhost'
+ @recipient = "test@localhost"
end
def test_url_for
UrlTestMailer.delivery_method = :test
- AppRoutes.draw do
- get ':controller(/:action(/:id))'
- get '/welcome' => 'foo#bar', as: 'welcome'
- get '/dummy_model' => 'foo#baz', as: 'dummy_model'
- end
-
# string
- assert_url_for 'http://foo/', 'http://foo/'
+ assert_url_for "http://foo/", "http://foo/"
# symbol
- assert_url_for '/welcome', :welcome
+ assert_url_for "/welcome", :welcome
# hash
- assert_url_for '/a/b/c', controller: 'a', action: 'b', id: 'c'
- assert_url_for '/a/b/c', {controller: 'a', action: 'b', id: 'c', only_path: true}, true
+ assert_url_for "/a/b/c", controller: "a", action: "b", id: "c"
+ assert_url_for "/a/b/c", { controller: "a", action: "b", id: "c", only_path: true }, true
# model
- assert_url_for '/dummy_model', DummyModel.new
+ assert_url_for "/dummy_model", DummyModel.new
# class
- assert_url_for '/dummy_model', DummyModel
+ assert_url_for "/dummy_model", DummyModel
# array
- assert_url_for '/dummy_model' , [DummyModel]
+ assert_url_for "/dummy_model", [DummyModel]
end
def test_signed_up_with_url
UrlTestMailer.delivery_method = :test
- AppRoutes.draw do
- get ':controller(/:action(/:id))'
- get '/welcome' => "foo#bar", as: "welcome"
- end
-
expected = new_mail
expected.to = @recipient
expected.subject = "[Signed up] Welcome #{@recipient}"
- expected.body = "Hello there,\n\nMr. #{@recipient}. Please see our greeting at http://example.com/welcome/greeting http://www.basecamphq.com/welcome\n\n<img alt=\"Somelogo\" src=\"/images/somelogo.png\" />"
+ expected.body = "Hello there,\n\nMr. #{@recipient}. Please see our greeting at http://example.com/welcome/greeting http://www.basecamphq.com/welcome\n\n<img src=\"/images/somelogo.png\" />"
expected.from = "system@loudthinking.com"
expected.date = Time.local(2004, 12, 12)
expected.content_type = "text/html"
@@ -124,15 +120,15 @@ class ActionMailerUrlTest < ActionMailer::TestCase
assert_nothing_raised { created = UrlTestMailer.signed_up_with_url(@recipient) }
assert_not_nil created
- expected.message_id = '<123@456>'
- created.message_id = '<123@456>'
+ expected.message_id = "<123@456>"
+ created.message_id = "<123@456>"
assert_dom_equal expected.encoded, created.encoded
assert_nothing_raised { UrlTestMailer.signed_up_with_url(@recipient).deliver_now }
assert_not_nil ActionMailer::Base.deliveries.first
delivered = ActionMailer::Base.deliveries.first
- delivered.message_id = '<123@456>'
+ delivered.message_id = "<123@456>"
assert_dom_equal expected.encoded, delivered.encoded
end
end