aboutsummaryrefslogtreecommitdiffstats
path: root/actionview
diff options
context:
space:
mode:
Diffstat (limited to 'actionview')
-rw-r--r--actionview/.gitignore7
-rw-r--r--actionview/CHANGELOG.md119
-rw-r--r--actionview/Rakefile9
-rw-r--r--actionview/actionview.gemspec2
-rw-r--r--actionview/app/assets/javascripts/README.md4
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee6
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/start.coffee3
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee6
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee4
-rw-r--r--actionview/lib/action_view/context.rb9
-rw-r--r--actionview/lib/action_view/digestor.rb16
-rw-r--r--actionview/lib/action_view/gem_version.rb6
-rw-r--r--actionview/lib/action_view/helpers.rb4
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb52
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb17
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/csp_helper.rb24
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb82
-rw-r--r--actionview/lib/action_view/helpers/debug_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb33
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb28
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb11
-rw-r--r--actionview/lib/action_view/helpers/javascript_helper.rb11
-rw-r--r--actionview/lib/action_view/helpers/record_tag_helper.rb23
-rw-r--r--actionview/lib/action_view/helpers/rendering_helper.rb1
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb9
-rw-r--r--actionview/lib/action_view/helpers/tags/base.rb12
-rw-r--r--actionview/lib/action_view/helpers/tags/color_field.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/select.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/translator.rb7
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb22
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb6
-rw-r--r--actionview/lib/action_view/railtie.rb18
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb2
-rw-r--r--actionview/lib/action_view/template.rb6
-rw-r--r--actionview/package.json2
-rw-r--r--actionview/test/actionpack/controller/render_test.rb24
-rw-r--r--actionview/test/activerecord/multifetch_cache_test.rb35
-rw-r--r--actionview/test/fixtures/digestor/comments/show.js.erb1
-rw-r--r--actionview/test/fixtures/public/.gitignore1
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb14
-rw-r--r--actionview/test/template/atom_feed_helper_test.rb2
-rw-r--r--actionview/test/template/capture_helper_test.rb30
-rw-r--r--actionview/test/template/date_helper_test.rb43
-rw-r--r--actionview/test/template/digestor_test.rb12
-rw-r--r--actionview/test/template/erb_util_test.rb12
-rw-r--r--actionview/test/template/form_helper/form_with_test.rb98
-rw-r--r--actionview/test/template/form_helper_test.rb64
-rw-r--r--actionview/test/template/form_options_helper_test.rb16
-rw-r--r--actionview/test/template/form_tag_helper_test.rb31
-rw-r--r--actionview/test/template/lookup_context_test.rb4
-rw-r--r--actionview/test/template/number_helper_test.rb72
-rw-r--r--actionview/test/template/output_safety_helper_test.rb12
-rw-r--r--actionview/test/template/partial_iteration_test.rb4
-rw-r--r--actionview/test/template/record_tag_helper_test.rb33
-rw-r--r--actionview/test/template/sanitize_helper_test.rb2
-rw-r--r--actionview/test/template/tag_helper_test.rb4
-rw-r--r--actionview/test/template/test_case_test.rb2
-rw-r--r--actionview/test/template/text_helper_test.rb16
-rw-r--r--actionview/test/template/translation_helper_test.rb13
-rw-r--r--actionview/test/template/url_helper_test.rb23
-rw-r--r--actionview/test/tmp/.keep0
-rw-r--r--actionview/test/ujs/.gitignore1
-rw-r--r--actionview/test/ujs/public/test/call-ajax.js3
-rw-r--r--actionview/test/ujs/public/test/call-remote-callbacks.js12
-rw-r--r--actionview/test/ujs/public/test/call-remote.js19
-rw-r--r--actionview/test/ujs/public/test/data-confirm.js42
-rw-r--r--actionview/test/ujs/public/test/data-disable-with.js27
-rw-r--r--actionview/test/ujs/public/test/data-disable.js25
-rw-r--r--actionview/test/ujs/public/test/data-remote.js23
-rw-r--r--actionview/test/ujs/public/test/override.js4
-rw-r--r--actionview/test/ujs/public/test/settings.js12
-rw-r--r--actionview/test/ujs/server.rb26
-rw-r--r--actionview/test/ujs/views/layouts/application.html.erb7
76 files changed, 899 insertions, 451 deletions
diff --git a/actionview/.gitignore b/actionview/.gitignore
index 0a04b29786..246aabbb7f 100644
--- a/actionview/.gitignore
+++ b/actionview/.gitignore
@@ -1,2 +1,5 @@
-/lib/assets/compiled
-/tmp
+/lib/assets/compiled/
+/log/
+/test/fixtures/public/absolute/
+/test/ujs/log/
+/tmp/
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index c38e11dc38..6d45cc1d8a 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,79 +1,112 @@
-* Allow the use of callable objects as group methods for grouped selects.
+* Fix issue with `button_to`'s `to_form_params`
- Until now, the `option_groups_from_collection_for_select` method was only able to
- handle method names as `group_method` and `group_label_method` parameters,
- it is now able to receive procs and other callable objects too.
+ `button_to` was throwing exception when invoked with `params` hash that
+ contains symbol and string keys. The reason for the exception was that
+ `to_form_params` was comparing the given symbol and string keys.
- *Jérémie Bonal*
+ The issue is fixed by turning all keys to strings inside
+ `to_form_params` before comparing them.
-* Add `preload_link_tag` helper
+ *Georgi Georgiev*
- This helper that allows to the browser to initiate early fetch of resources
- (different to the specified in `javascript_include_tag` and `stylesheet_link_tag`).
- Additionally, this sends Early Hints if supported by browser.
+* Mark arrays of translations as trusted safe by using the `_html` suffix.
- *Guillermo Iguaran*
+ Example:
-## Rails 5.2.0.beta2 (November 28, 2017) ##
+ en:
+ foo_html:
+ - "One"
+ - "<strong>Two</strong>"
+ - "Three &#128075; &#128578;"
-* No changes.
+ *Juan Broullon*
+* Add `year_format` option to date_select tag. This option makes it possible to customize year
+ names. Lambda should be passed to use this option.
-## Rails 5.2.0.beta1 (November 27, 2017) ##
+ Example:
-* Change `form_with` to generates ids by default.
+ date_select('user_birthday', '', start_year: 1998, end_year: 2000, year_format: ->year { "Heisei #{year - 1988}" })
- When `form_with` was introduced we disabled the automatic generation of ids
- that was enabled in `form_for`. This usually is not an good idea since labels don't work
- when the input doesn't have an id and it made harder to test with Capybara.
+ The HTML produced:
- You can still disable the automatic generation of ids setting `config.action_view.form_with_generates_ids`
- to `false.`
+ <select id="user_birthday__1i" name="user_birthday[(1i)]">
+ <option value="1998">Heisei 10</option>
+ <option value="1999">Heisei 11</option>
+ <option value="2000">Heisei 12</option>
+ </select>
+ /* The rest is omitted */
- *Nick Pezza*
+ *Koki Ryu*
-* Fix issues with `field_error_proc` wrapping `optgroup` and select divider `option`.
+* Fix JavaScript views rendering does not work with Firefox when using
+ Content Security Policy.
- Fixes #31088
+ Fixes #32577.
- *Matthias Neumayr*
+ *Yuji Yaginuma*
-* Remove deprecated Erubis ERB handler.
+* Add the `nonce: true` option for `javascript_include_tag` helper to
+ support automatic nonce generation for Content Security Policy.
+ Works the same way as `javascript_tag nonce: true` does.
- *Rafael Mendonça França*
+ *Yaroslav Markin*
-* Remove default `alt` text generation.
+* Remove `ActionView::Helpers::RecordTagHelper`.
- Fixes #30096
+ *Yoshiyuki Hirano*
- *Cameron Cundiff*
+* Disable `ActionView::Template` finalizers in test environment.
-* Add `srcset` option to `image_tag` helper.
+ Template finalization can be expensive in large view test suites.
+ Add a configuration option,
+ `action_view.finalize_compiled_template_methods`, and turn it off in
+ the test environment.
- *Roberto Miranda*
+ *Simon Coffey*
-* Fix issues with scopes and engine on `current_page?` method.
+* Extract the `confirm` call in its own, overridable method in `rails_ujs`.
+ Example :
+ Rails.confirm = function(message, element) {
+ return (my_bootstrap_modal_confirm(message));
+ }
- Fixes #29401.
+ *Mathieu Mahé*
- *Nikita Savrov*
+* Enable select tag helper to mark `prompt` option as `selected` and/or `disabled` for `required`
+ field. Example:
-* Generate field ids in `collection_check_boxes` and `collection_radio_buttons`.
+ select :post,
+ :category,
+ ["lifestyle", "programming", "spiritual"],
+ { selected: "", disabled: "", prompt: "Choose one" },
+ { required: true }
- This makes sure that the labels are linked up with the fields.
+ Placeholder option would be selected and disabled. The HTML produced:
- Fixes #29014.
+ <select required="required" name="post[category]" id="post_category">
+ <option disabled="disabled" selected="selected" value="">Choose one</option>
+ <option value="lifestyle">lifestyle</option>
+ <option value="programming">programming</option>
+ <option value="spiritual">spiritual</option></select>
- *Yuji Yaginuma*
+ *Sergey Prikhodko*
+
+* Don't enforce UTF-8 by default.
+
+ With the disabling of TLS 1.0 by most major websites, continuing to run
+ IE8 or lower becomes increasingly difficult so default to not enforcing
+ UTF-8 encoding as it's not relevant to other browsers.
+
+ *Andrew White*
-* Add `:json` type to `auto_discovery_link_tag` to support [JSON Feeds](https://jsonfeed.org/version/1)
+* Change translation key of `submit_tag` from `module_name_class_name` to `module_name/class_name`.
- *Mike Gunderloy*
+ *Rui Onodera*
-* Update `distance_of_time_in_words` helper to display better error messages
- for bad input.
+* Rails 6 requires Ruby 2.4.1 or newer.
- *Jay Hayes*
+ *Jeremy Daer*
-Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionview/CHANGELOG.md) for previous changes.
+Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/actionview/CHANGELOG.md) for previous changes.
diff --git a/actionview/Rakefile b/actionview/Rakefile
index 8650f541b0..7851a2b6bf 100644
--- a/actionview/Rakefile
+++ b/actionview/Rakefile
@@ -29,6 +29,7 @@ namespace :test do
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
end
+ desc "Run tests for rails-ujs"
task :ujs do
begin
Dir.mkdir("log")
@@ -56,7 +57,7 @@ namespace :test do
end
namespace :integration do
- desc "ActiveRecord Integration Tests"
+ # Active Record Integration Tests
Rake::TestTask.new(:active_record) do |t|
t.libs << "test"
t.test_files = Dir.glob("test/activerecord/*_test.rb")
@@ -65,7 +66,7 @@ namespace :test do
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
end
- desc "ActionPack Integration Tests"
+ # Action Pack Integration Tests
Rake::TestTask.new(:action_pack) do |t|
t.libs << "test"
t.test_files = Dir.glob("test/actionpack/**/*_test.rb")
@@ -106,7 +107,7 @@ namespace :assets do
end
print "[verify] #{file} is a UMD module "
- if pathname.read =~ /module\.exports.*define\.amd/m
+ if /module\.exports.*define\.amd/m.match?(pathname.read)
puts "[OK]"
else
$stderr.puts "[FAIL]"
@@ -130,7 +131,7 @@ namespace :assets do
end
task :lines do
- load File.join(File.expand_path("..", __dir__), "/tools/line_statistics")
+ load File.expand_path("../tools/line_statistics", __dir__)
files = FileList["lib/**/*.rb"]
CodeTools::LineStatistics.new(files).print_loc
end
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index b99137fcf6..49ee1a292b 100644
--- a/actionview/actionview.gemspec
+++ b/actionview/actionview.gemspec
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
s.summary = "Rendering framework putting the V in MVC (part of Rails)."
s.description = "Simple, battle-tested conventions and helpers for building web pages."
- s.required_ruby_version = ">= 2.2.2"
+ s.required_ruby_version = ">= 2.4.1"
s.license = "MIT"
diff --git a/actionview/app/assets/javascripts/README.md b/actionview/app/assets/javascripts/README.md
index 8198011b02..b74fa1afad 100644
--- a/actionview/app/assets/javascripts/README.md
+++ b/actionview/app/assets/javascripts/README.md
@@ -23,6 +23,8 @@ Note that the `data` attributes this library adds are a feature of HTML5. If you
yarn add rails-ujs
+Ensure that `.yarnclean` does not include `assets` if you use [yarn autoclean](https://yarnpkg.com/lang/en/docs/cli/autoclean/).
+
## Usage
### Asset pipeline
@@ -50,6 +52,6 @@ Run `bundle exec rake ujs:server` first, and then run the web tests by visiting
rails-ujs is released under the [MIT License](MIT-LICENSE).
-[data]: http://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes "Embedding custom non-visible data with the data-* attributes"
+[data]: https://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data-with-the-data-attributes "Embedding custom non-visible data with the data-* attributes"
[validator]: http://validator.w3.org/
[csrf]: http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html
diff --git a/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee b/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee
index 72b5aaa218..0738ffcdc9 100644
--- a/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee
@@ -5,6 +5,10 @@
Rails.handleConfirm = (e) ->
stopEverything(e) unless allowAction(this)
+# Default confirm dialog, may be overridden with custom confirm dialog in Rails.confirm
+Rails.confirm = (message, element) ->
+ confirm(message)
+
# For 'data-confirm' attribute:
# - Fires `confirm` event
# - Shows the confirmation dialog
@@ -20,7 +24,7 @@ allowAction = (element) ->
answer = false
if fire(element, 'confirm')
- try answer = confirm(message)
+ try answer = Rails.confirm(message, element)
callback = fire(element, 'confirm:complete', [answer])
answer and callback
diff --git a/actionview/app/assets/javascripts/rails-ujs/start.coffee b/actionview/app/assets/javascripts/rails-ujs/start.coffee
index 55595ac96f..32a915ac0b 100644
--- a/actionview/app/assets/javascripts/rails-ujs/start.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/start.coffee
@@ -9,7 +9,8 @@
} = Rails
# For backward compatibility
-if jQuery? and jQuery.ajax? and not jQuery.rails
+if jQuery? and jQuery.ajax?
+ throw new Error('If you load both jquery_ujs and rails-ujs, use rails-ujs only.') if jQuery.rails
jQuery.rails = Rails
jQuery.ajaxPrefilter (options, originalOptions, xhr) ->
CSRFProtection(xhr) unless options.crossDomain
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
index cc0e037428..019bda635a 100644
--- a/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
@@ -1,7 +1,8 @@
+#= require ./csp
#= require ./csrf
#= require ./event
-{ CSRFProtection, fire } = Rails
+{ cspNonce, CSRFProtection, fire } = Rails
AcceptHeaders =
'*': '*/*'
@@ -65,9 +66,10 @@ processResponse = (response, type) ->
try response = JSON.parse(response)
else if type.match(/\b(?:java|ecma)script\b/)
script = document.createElement('script')
+ script.setAttribute('nonce', cspNonce())
script.text = response
document.head.appendChild(script).parentNode.removeChild(script)
- else if type.match(/\b(xml|html|svg)\b/)
+ else if type.match(/\bxml\b/)
parser = new DOMParser()
type = type.replace(/;.+/, '') # remove something like ';charset=utf-8'
try response = parser.parseFromString(response, type)
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee
new file mode 100644
index 0000000000..8d2d6ce447
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee
@@ -0,0 +1,4 @@
+# Content-Security-Policy nonce for inline scripts
+cspNonce = Rails.cspNonce = ->
+ meta = document.querySelector('meta[name=csp-nonce]')
+ meta and meta.content
diff --git a/actionview/lib/action_view/context.rb b/actionview/lib/action_view/context.rb
index 665a9e3171..3c605c3ee3 100644
--- a/actionview/lib/action_view/context.rb
+++ b/actionview/lib/action_view/context.rb
@@ -10,10 +10,11 @@ module ActionView
# Action View contexts are supplied to Action Controller to render a template.
# The default Action View context is ActionView::Base.
#
- # In order to work with ActionController, a Context must just include this module.
- # The initialization of the variables used by the context (@output_buffer, @view_flow,
- # and @virtual_path) is responsibility of the object that includes this module
- # (although you can call _prepare_context defined below).
+ # In order to work with Action Controller, a Context must just include this
+ # module. The initialization of the variables used by the context
+ # (@output_buffer, @view_flow, and @virtual_path) is responsibility of the
+ # object that includes this module (although you can call _prepare_context
+ # defined below).
module Context
include CompiledTemplates
attr_accessor :output_buffer, :view_flow
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 1cf0bd3016..39cdecb9e4 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -1,8 +1,6 @@
# frozen_string_literal: true
-require "concurrent/map"
require "action_view/dependency_tracker"
-require "monitor"
module ActionView
class Digestor
@@ -46,10 +44,7 @@ module ActionView
def tree(name, finder, partial = false, seen = {})
logical_name = name.gsub(%r|/_|, "/")
- options = {}
- options[:formats] = [finder.rendered_format] if finder.rendered_format
-
- if template = finder.disable_cache { finder.find_all(logical_name, [], partial, [], options).first }
+ if template = find_template(finder, logical_name, [], partial, [])
finder.rendered_format ||= template.formats.first
if node = seen[template.identifier] # handle cycles in the tree
@@ -71,6 +66,15 @@ module ActionView
seen[name] ||= Missing.new(name, logical_name, nil)
end
end
+
+ private
+ def find_template(finder, name, prefixes, partial, keys)
+ finder.disable_cache do
+ format = finder.rendered_format
+ result = finder.find_all(name, prefixes, partial, keys, formats: [format]).first if format
+ result || finder.find_all(name, prefixes, partial, keys).first
+ end
+ end
end
class Node
diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb
index ff7f2bb853..77ae444a58 100644
--- a/actionview/lib/action_view/gem_version.rb
+++ b/actionview/lib/action_view/gem_version.rb
@@ -7,10 +7,10 @@ module ActionView
end
module VERSION
- MAJOR = 5
- MINOR = 2
+ MAJOR = 6
+ MINOR = 0
TINY = 0
- PRE = "beta2"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionview/lib/action_view/helpers.rb b/actionview/lib/action_view/helpers.rb
index 46f20c4277..0d77f74171 100644
--- a/actionview/lib/action_view/helpers.rb
+++ b/actionview/lib/action_view/helpers.rb
@@ -13,6 +13,7 @@ module ActionView #:nodoc:
autoload :CacheHelper
autoload :CaptureHelper
autoload :ControllerHelper
+ autoload :CspHelper
autoload :CsrfHelper
autoload :DateHelper
autoload :DebugHelper
@@ -22,7 +23,6 @@ module ActionView #:nodoc:
autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
autoload :NumberHelper
autoload :OutputSafetyHelper
- autoload :RecordTagHelper
autoload :RenderingHelper
autoload :SanitizeHelper
autoload :TagHelper
@@ -46,6 +46,7 @@ module ActionView #:nodoc:
include CacheHelper
include CaptureHelper
include ControllerHelper
+ include CspHelper
include CsrfHelper
include DateHelper
include DebugHelper
@@ -55,7 +56,6 @@ module ActionView #:nodoc:
include JavaScriptHelper
include NumberHelper
include OutputSafetyHelper
- include RecordTagHelper
include RenderingHelper
include SanitizeHelper
include TagHelper
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index da630129cb..14bd8ffa84 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -47,14 +47,16 @@ module ActionView
# When the last parameter is a hash you can add HTML attributes using that
# parameter. The following options are supported:
#
- # * <tt>:extname</tt> - Append an extension to the generated url unless the extension
- # already exists. This only applies for relative urls.
- # * <tt>:protocol</tt> - Sets the protocol of the generated url, this option only
- # applies when a relative url and +host+ options are provided.
- # * <tt>:host</tt> - When a relative url is provided the host is added to the
+ # * <tt>:extname</tt> - Append an extension to the generated URL unless the extension
+ # already exists. This only applies for relative URLs.
+ # * <tt>:protocol</tt> - Sets the protocol of the generated URL. This option only
+ # applies when a relative URL and +host+ options are provided.
+ # * <tt>:host</tt> - When a relative URL is provided the host is added to the
# that path.
# * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
# when it is set to true.
+ # * <tt>:nonce<tt> - When set to true, adds an automatic nonce value if
+ # you have Content Security Policy enabled.
#
# ==== Examples
#
@@ -79,6 +81,9 @@ module ActionView
#
# javascript_include_tag "http://www.example.com/xmlhr.js"
# # => <script src="http://www.example.com/xmlhr.js"></script>
+ #
+ # javascript_include_tag "http://www.example.com/xmlhr.js", nonce: true
+ # # => <script src="http://www.example.com/xmlhr.js" nonce="..."></script>
def javascript_include_tag(*sources)
options = sources.extract_options!.stringify_keys
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
@@ -90,6 +95,9 @@ module ActionView
tag_options = {
"src" => href
}.merge!(options)
+ if tag_options["nonce"] == true
+ tag_options["nonce"] = content_security_policy_nonce
+ end
content_tag("script".freeze, "", tag_options)
}.join("\n").html_safe
@@ -105,7 +113,7 @@ module ActionView
# to "screen", so you must explicitly set it to "all" for the stylesheet(s) to
# apply to all media types.
#
- # If the server supports Early Hints header links for these assets will be
+ # If the server supports Early Hints header links for these assets will be
# automatically pushed.
#
# stylesheet_link_tag "style"
@@ -133,7 +141,7 @@ module ActionView
sources_tags = sources.uniq.map { |source|
href = path_to_stylesheet(source, path_options)
- early_hints_links << "<#{href}>; rel=preload; as=stylesheet"
+ early_hints_links << "<#{href}>; rel=preload; as=style"
tag_options = {
"rel" => "stylesheet",
"media" => "screen",
@@ -224,8 +232,8 @@ module ActionView
end
# Returns a link tag that browsers can use to preload the +source+.
- # The +source+ can be the path of an resource managed by asset pipeline,
- # a full path or an URI.
+ # The +source+ can be the path of a resource managed by asset pipeline,
+ # a full path, or an URI.
#
# ==== Options
#
@@ -285,7 +293,7 @@ module ActionView
end
# Returns an HTML image tag for the +source+. The +source+ can be a full
- # path, a file or an Active Storage attachment.
+ # path, a file, or an Active Storage attachment.
#
# ==== Options
#
@@ -325,9 +333,9 @@ module ActionView
#
# image_tag(user.avatar)
# # => <img src="/rails/active_storage/blobs/.../tiger.jpg" />
- # image_tag(user.avatar.variant(resize: "100x100"))
+ # image_tag(user.avatar.variant(resize_to_fit: [100, 100]))
# # => <img src="/rails/active_storage/variants/.../tiger.jpg" />
- # image_tag(user.avatar.variant(resize: "100x100"), size: '100')
+ # image_tag(user.avatar.variant(resize_to_fit: [100, 100]), size: '100')
# # => <img width="100" height="100" src="/rails/active_storage/variants/.../tiger.jpg" />
def image_tag(source, options = {})
options = options.symbolize_keys
@@ -373,12 +381,13 @@ module ActionView
# Returns an HTML video tag for the +sources+. If +sources+ is a string,
# a single video tag will be returned. If +sources+ is an array, a video
# tag with nested source tags for each source will be returned. The
- # +sources+ can be full paths or files that exists in your public videos
+ # +sources+ can be full paths or files that exist in your public videos
# directory.
#
# ==== Options
- # You can add HTML attributes using the +options+. The +options+ supports
- # two additional keys for convenience and conformance:
+ #
+ # When the last parameter is a hash you can add HTML attributes using that
+ # parameter. The following options are supported:
#
# * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
# before the video loads. The path is calculated like the +src+ of +image_tag+.
@@ -395,7 +404,7 @@ module ActionView
# video_tag("trailer.ogg")
# # => <video src="/videos/trailer.ogg"></video>
# video_tag("trailer.ogg", controls: true, preload: 'none')
- # # => <video preload="none" controls="controls" src="/videos/trailer.ogg" ></video>
+ # # => <video preload="none" controls="controls" src="/videos/trailer.ogg"></video>
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png"></video>
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png", poster_skip_pipeline: true)
@@ -422,9 +431,14 @@ module ActionView
end
end
- # Returns an HTML audio tag for the +source+.
- # The +source+ can be full path or file that exists in
- # your public audios directory.
+ # Returns an HTML audio tag for the +sources+. If +sources+ is a string,
+ # a single audio tag will be returned. If +sources+ is an array, an audio
+ # tag with nested source tags for each source will be returned. The
+ # +sources+ can be full paths or files that exist in your public audios
+ # directory.
+ #
+ # When the last parameter is a hash you can add HTML attributes using that
+ # parameter.
#
# audio_tag("sound")
# # => <audio src="/audios/sound"></audio>
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index f7690104ee..1808765666 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -6,7 +6,7 @@ module ActionView
# = Action View Asset URL Helpers
module Helpers #:nodoc:
# This module provides methods for generating asset paths and
- # urls.
+ # URLs.
#
# image_path("rails.png")
# # => "/assets/rails.png"
@@ -57,8 +57,8 @@ module ActionView
# You can read more about setting up your DNS CNAME records from your ISP.
#
# Note: This is purely a browser performance optimization and is not meant
- # for server load balancing. See http://www.die.net/musings/page_load_time/
- # for background and http://www.browserscope.org/?category=network for
+ # for server load balancing. See https://www.die.net/musings/page_load_time/
+ # for background and https://www.browserscope.org/?category=network for
# connection limit data.
#
# Alternatively, you can exert more control over the asset host by setting
@@ -97,9 +97,10 @@ module ActionView
# still sending assets for plain HTTP requests from asset hosts. If you don't
# have SSL certificates for each of the asset hosts this technique allows you
# to avoid warnings in the client about mixed media.
- # Note that the request parameter might not be supplied, e.g. when the assets
- # are precompiled via a Rake task. Make sure to use a +Proc+ instead of a lambda,
- # since a +Proc+ allows missing parameters and sets them to +nil+.
+ # Note that the +request+ parameter might not be supplied, e.g. when the assets
+ # are precompiled with the command `rails assets:precompile`. Make sure to use a
+ # +Proc+ instead of a lambda, since a +Proc+ allows missing parameters and sets them
+ # to +nil+.
#
# config.action_controller.asset_host = Proc.new { |source, request|
# if request && request.ssl?
@@ -149,13 +150,13 @@ module ActionView
# Below lists scenarios that apply to +asset_path+ whether or not you're
# using the asset pipeline.
#
- # - All fully qualified urls are returned immediately. This bypasses the
+ # - All fully qualified URLs are returned immediately. This bypasses the
# asset pipeline and all other behavior described.
#
# asset_path("http://www.example.com/js/xmlhr.js") # => "http://www.example.com/js/xmlhr.js"
#
# - All assets that begin with a forward slash are assumed to be full
- # urls and will not be expanded. This will bypass the asset pipeline.
+ # URLs and will not be expanded. This will bypass the asset pipeline.
#
# asset_path("/foo.png") # => "/foo.png"
#
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index 3cbb1ed1a7..15d187a9ec 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -201,7 +201,7 @@ module ActionView
end
# This helper returns the name of a cache key for a given fragment cache
- # call. By supplying +skip_digest:+ true to cache, the digestion of cache
+ # call. By supplying <tt>skip_digest: true</tt> to cache, the digestion of cache
# fragments can be manually bypassed. This is useful when cache fragments
# cannot be manually expired unless you know the exact key which is the
# case when using memcached.
diff --git a/actionview/lib/action_view/helpers/csp_helper.rb b/actionview/lib/action_view/helpers/csp_helper.rb
new file mode 100644
index 0000000000..e2e065c218
--- /dev/null
+++ b/actionview/lib/action_view/helpers/csp_helper.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module ActionView
+ # = Action View CSP Helper
+ module Helpers #:nodoc:
+ module CspHelper
+ # Returns a meta tag "csp-nonce" with the per-session nonce value
+ # for allowing inline <script> tags.
+ #
+ # <head>
+ # <%= csp_meta_tag %>
+ # </head>
+ #
+ # This is used by the Rails UJS helper to create dynamically
+ # loaded inline <script> elements.
+ #
+ def csp_meta_tag
+ if content_security_policy?
+ tag("meta", name: "csp-nonce", content: content_security_policy_nonce)
+ end
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 09040ccbc4..4de4fafde0 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -116,7 +116,7 @@ module ActionView
when 10..19 then locale.t :less_than_x_seconds, count: 20
when 20..39 then locale.t :half_a_minute
when 40..59 then locale.t :less_than_x_minutes, count: 1
- else locale.t :x_minutes, count: 1
+ else locale.t :x_minutes, count: 1
end
when 2...45 then locale.t :x_minutes, count: distance_in_minutes
@@ -131,7 +131,7 @@ module ActionView
when 43200...86400 then locale.t :about_x_months, count: (distance_in_minutes.to_f / 43200.0).round
# 60 days up to 365 days
when 86400...525600 then locale.t :x_months, count: (distance_in_minutes.to_f / 43200.0).round
- else
+ else
from_year = from_time.year
from_year += 1 if from_time.month >= 3
to_year = to_time.year
@@ -205,6 +205,7 @@ module ActionView
# * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if
# you are creating new record. While editing existing record, <tt>:end_year</tt> defaults to
# the current selected year plus 5.
+ # * <tt>:year_format</tt> - Set format of years for year select. Lambda should be passed.
# * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day
# as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the
# first of the given month in order to not create invalid dates like 31 February.
@@ -275,6 +276,9 @@ module ActionView
# # Generates a date select with custom prompts.
# date_select("article", "written_on", prompt: { day: 'Select day', month: 'Select month', year: 'Select year' })
#
+ # # Generates a date select with custom year format.
+ # date_select("article", "written_on", year_format: ->(year) { "Heisei #{year - 1988}" })
+ #
# The selects are prepared for multi-parameter assignment to an Active Record object.
#
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
@@ -302,15 +306,15 @@ module ActionView
# time_select("article", "start_time", include_seconds: true)
#
# # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30, and 45.
- # time_select 'game', 'game_time', {minute_step: 15}
+ # time_select 'game', 'game_time', { minute_step: 15 }
#
# # Creates a time select tag with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
- # time_select("article", "written_on", prompt: {hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds'})
- # time_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours
+ # time_select("article", "written_on", prompt: { hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds' })
+ # time_select("article", "written_on", prompt: { hour: true }) # generic prompt for hours
# time_select("article", "written_on", prompt: true) # generic prompts for all
#
# # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM.
- # time_select 'game', 'game_time', {ampm: true}
+ # time_select 'game', 'game_time', { ampm: true }
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
#
@@ -346,8 +350,8 @@ module ActionView
# datetime_select("article", "written_on", discard_type: true)
#
# # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
- # datetime_select("article", "written_on", prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
- # datetime_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours
+ # datetime_select("article", "written_on", prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
+ # datetime_select("article", "written_on", prompt: { hour: true }) # generic prompt for hours
# datetime_select("article", "written_on", prompt: true) # generic prompts for all
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
@@ -397,8 +401,8 @@ module ActionView
# select_datetime(my_date_time, prefix: 'payday')
#
# # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
- # select_datetime(my_date_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
- # select_datetime(my_date_time, prompt: {hour: true}) # generic prompt for hours
+ # select_datetime(my_date_time, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
+ # select_datetime(my_date_time, prompt: { hour: true }) # generic prompt for hours
# select_datetime(my_date_time, prompt: true) # generic prompts for all
def select_datetime(datetime = Time.current, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_datetime
@@ -436,8 +440,8 @@ module ActionView
# select_date(my_date, prefix: 'payday')
#
# # Generates a date select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
- # select_date(my_date, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
- # select_date(my_date, prompt: {hour: true}) # generic prompt for hours
+ # select_date(my_date, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
+ # select_date(my_date, prompt: { hour: true }) # generic prompt for hours
# select_date(my_date, prompt: true) # generic prompts for all
def select_date(date = Date.current, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_date
@@ -476,8 +480,8 @@ module ActionView
# select_time(my_time, start_hour: 2, end_hour: 14)
#
# # Generates a time select with a custom prompt. Use <tt>:prompt</tt> to true for generic prompts.
- # select_time(my_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
- # select_time(my_time, prompt: {hour: true}) # generic prompt for hours
+ # select_time(my_time, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
+ # select_time(my_time, prompt: { hour: true }) # generic prompt for hours
# select_time(my_time, prompt: true) # generic prompts for all
def select_time(datetime = Time.current, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_time
@@ -668,8 +672,6 @@ module ActionView
# <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time>
# time_tag Date.yesterday, 'Yesterday' # =>
# <time datetime="2010-11-03">Yesterday</time>
- # time_tag Date.today, pubdate: true # =>
- # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
# time_tag Date.today, datetime: Date.today.strftime('%G-W%V') # =>
# <time datetime="2010-W44">November 04, 2010</time>
#
@@ -681,9 +683,8 @@ module ActionView
options = args.extract_options!
format = options.delete(:format) || :long
content = args.first || I18n.l(date_or_time, format: format)
- datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601
- content_tag("time".freeze, content, options.reverse_merge(datetime: datetime), &block)
+ content_tag("time".freeze, content, options.reverse_merge(datetime: date_or_time.iso8601), &block)
end
private
@@ -851,7 +852,7 @@ module ActionView
raise ArgumentError, "There are too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter."
end
- build_options_and_select(:year, val, options)
+ build_select(:year, build_year_options(val, options))
end
end
@@ -934,6 +935,21 @@ module ActionView
end
end
+ # Looks up year names by number.
+ #
+ # year_name(1998) # => 1998
+ #
+ # If the <tt>:year_format</tt> option is passed:
+ #
+ # year_name(1998) # => "Heisei 10"
+ def year_name(number)
+ if year_format_lambda = @options[:year_format]
+ year_format_lambda.call(number)
+ else
+ number
+ end
+ end
+
def date_order
@date_order ||= @options[:order] || translated_date_order
end
@@ -996,6 +1012,34 @@ module ActionView
(select_options.join("\n") + "\n").html_safe
end
+ # Build select option HTML for year.
+ # If <tt>year_format</tt> option is not passed
+ # build_year_options(1998, start: 1998, end: 2000)
+ # => "<option value="1998" selected="selected">1998</option>
+ # <option value="1999">1999</option>
+ # <option value="2000">2000</option>"
+ #
+ # If <tt>year_format</tt> option is passed
+ # build_year_options(1998, start: 1998, end: 2000, year_format: ->year { "Heisei #{ year - 1988 }" })
+ # => "<option value="1998" selected="selected">Heisei 10</option>
+ # <option value="1999">Heisei 11</option>
+ # <option value="2000">Heisei 12</option>"
+ def build_year_options(selected, options = {})
+ start = options.delete(:start)
+ stop = options.delete(:end)
+ step = options.delete(:step)
+
+ select_options = []
+ start.step(stop, step) do |value|
+ tag_options = { value: value }
+ tag_options[:selected] = "selected" if selected == value
+ text = year_name(value)
+ select_options << content_tag("option".freeze, text, tag_options)
+ end
+
+ (select_options.join("\n") + "\n").html_safe
+ end
+
# Builds select tag from date type and HTML select options.
# build_select(:month, "<option value="1">January</option>...")
# => "<select id="post_written_on_2i" name="post[written_on(2i)]">
diff --git a/actionview/lib/action_view/helpers/debug_helper.rb b/actionview/lib/action_view/helpers/debug_helper.rb
index 52dff1f750..88ceba414b 100644
--- a/actionview/lib/action_view/helpers/debug_helper.rb
+++ b/actionview/lib/action_view/helpers/debug_helper.rb
@@ -24,7 +24,7 @@ module ActionView
# created_at:
# </pre>
def debug(object)
- Marshal::dump(object)
+ Marshal.dump(object)
object = ERB::Util.html_escape(object.to_yaml)
content_tag(:pre, object, class: "debug_dump")
rescue # errors from Marshal or YAML
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 6185aa133f..07f3d98322 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -19,7 +19,7 @@ module ActionView
# compared to using vanilla HTML.
#
# Typically, a form designed to create or update a resource reflects the
- # identity of the resource in several ways: (i) the url that the form is
+ # identity of the resource in several ways: (i) the URL that the form is
# sent to (the form element's +action+ attribute) should result in a request
# being routed to the appropriate controller action (with the appropriate <tt>:id</tt>
# parameter in the case of an existing resource), (ii) input fields should
@@ -166,7 +166,7 @@ module ActionView
# So for example you may use a named route directly. When the model is
# represented by a string or symbol, as in the example above, if the
# <tt>:url</tt> option is not specified, by default the form will be
- # sent back to the current url (We will describe below an alternative
+ # sent back to the current URL (We will describe below an alternative
# resource-oriented usage of +form_for+ in which the URL does not need
# to be specified explicitly).
# * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
@@ -608,10 +608,10 @@ module ActionView
# This is helpful when fragment-caching the form. Remote forms
# get the authenticity token from the <tt>meta</tt> tag, so embedding is
# unnecessary unless you support browsers without JavaScript.
- # * <tt>:local</tt> - By default form submits are remote and unobstrusive XHRs.
+ # * <tt>:local</tt> - By default form submits are remote and unobtrusive XHRs.
# Disable remote submits with <tt>local: true</tt>.
- # * <tt>:skip_enforcing_utf8</tt> - By default a hidden field named +utf8+
- # is output to enforce UTF-8 submits. Set to true to skip the field.
+ # * <tt>:skip_enforcing_utf8</tt> - If set to true, a hidden input with name
+ # utf8 is not output.
# * <tt>:builder</tt> - Override the object used to build the form.
# * <tt>:id</tt> - Optional HTML id attribute.
# * <tt>:class</tt> - Optional HTML class attribute.
@@ -1014,14 +1014,13 @@ module ActionView
# <%= fields :comment do |fields| %>
# <%= fields.text_field :body %>
# <% end %>
- # # => <input type="text" name="comment[body]>
+ # # => <input type="text" name="comment[body]">
#
# # Using a model infers the scope and assigns field values:
- # <%= fields model: Comment.new(body: "full bodied") do |fields| %<
+ # <%= fields model: Comment.new(body: "full bodied") do |fields| %>
# <%= fields.text_field :body %>
# <% end %>
- # # =>
- # <input type="text" name="comment[body] value="full bodied">
+ # # => <input type="text" name="comment[body]" value="full bodied">
#
# # Using +fields+ with +form_with+:
# <%= form_with model: @post do |form| %>
@@ -1520,10 +1519,10 @@ module ActionView
private
def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
- skip_enforcing_utf8: false, **options)
+ skip_enforcing_utf8: nil, **options)
html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
- html_options[:enforce_utf8] = !skip_enforcing_utf8
+ html_options[:enforce_utf8] = !skip_enforcing_utf8 unless skip_enforcing_utf8.nil?
html_options[:enctype] = "multipart/form-data" if html_options.delete(:multipart)
@@ -1659,6 +1658,7 @@ module ActionView
@nested_child_index = {}
@object_name, @object, @template, @options = object_name, object, template, options
@default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {}
+ @default_html_options = @default_options.except(:skip_default_ids, :allow_method_names_outside_object)
convert_to_legacy_options(@options)
@@ -1971,7 +1971,7 @@ module ActionView
convert_to_legacy_options(options)
- fields_for(scope || model, model, **options, &block)
+ fields_for(scope || model, model, options, &block)
end
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
@@ -2247,7 +2247,7 @@ module ActionView
@template.button_tag(value, options, &block)
end
- def emitted_hidden_id?
+ def emitted_hidden_id? # :nodoc:
@emitted_hidden_id ||= nil
end
@@ -2267,7 +2267,12 @@ module ActionView
end
defaults = []
- defaults << :"helpers.submit.#{object_name}.#{key}"
+ # Object is a model and it is not overwritten by as and scope option.
+ if object.respond_to?(:model_name) && object_name.to_s == model.downcase
+ defaults << :"helpers.submit.#{object.model_name.i18n_key}.#{key}"
+ else
+ defaults << :"helpers.submit.#{object_name}.#{key}"
+ end
defaults << :"helpers.submit.#{key}"
defaults << "#{key.to_s.humanize} #{model}"
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index fe5e0b693e..7884a8d997 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -16,7 +16,7 @@ module ActionView
#
# * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
#
- # select("post", "category", Post::CATEGORIES, {include_blank: true})
+ # select("post", "category", Post::CATEGORIES, { include_blank: true })
#
# could become:
#
@@ -30,7 +30,7 @@ module ActionView
#
# Example with <tt>@post.person_id => 2</tt>:
#
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: 'None'})
+ # select("post", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: 'None' })
#
# could become:
#
@@ -43,7 +43,7 @@ module ActionView
#
# * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
#
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {prompt: 'Select Person'})
+ # select("post", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { prompt: 'Select Person' })
#
# could become:
#
@@ -69,7 +69,7 @@ module ActionView
#
# * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
#
- # select("post", "category", Post::CATEGORIES, {disabled: 'restricted'})
+ # select("post", "category", Post::CATEGORIES, { disabled: 'restricted' })
#
# could become:
#
@@ -82,7 +82,7 @@ module ActionView
#
# When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
#
- # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: -> (category) { category.archived? }})
+ # collection_select(:post, :category_id, Category.all, :id, :name, { disabled: -> (category) { category.archived? } })
#
# If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
# <select name="post[category_id]" id="post_category_id">
@@ -107,7 +107,7 @@ module ActionView
#
# For example:
#
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { include_blank: true })
+ # select("post", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: true })
#
# would become:
#
@@ -323,12 +323,12 @@ module ActionView
#
# You can optionally provide HTML attributes as the last element of the array.
#
- # options_for_select([ "Denmark", ["USA", {class: 'bold'}], "Sweden" ], ["USA", "Sweden"])
+ # options_for_select([ "Denmark", ["USA", { class: 'bold' }], "Sweden" ], ["USA", "Sweden"])
# # => <option value="Denmark">Denmark</option>
# # => <option value="USA" class="bold" selected="selected">USA</option>
# # => <option value="Sweden" selected="selected">Sweden</option>
#
- # options_for_select([["Dollar", "$", {class: "bold"}], ["Kroner", "DKK", {onclick: "alert('HI');"}]])
+ # options_for_select([["Dollar", "$", { class: "bold" }], ["Kroner", "DKK", { onclick: "alert('HI');" }]])
# # => <option value="$" class="bold">Dollar</option>
# # => <option value="DKK" onclick="alert('HI');">Kroner</option>
#
@@ -820,7 +820,7 @@ module ActionView
#
# Please refer to the documentation of the base helper for details.
def select(method, choices = nil, options = {}, html_options = {}, &block)
- @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options), &block)
+ @template.select(@object_name, method, choices, objectify_options(options), @default_html_options.merge(html_options), &block)
end
# Wraps ActionView::Helpers::FormOptionsHelper#collection_select for form builders:
@@ -832,7 +832,7 @@ module ActionView
#
# Please refer to the documentation of the base helper for details.
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
- @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
+ @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options))
end
# Wraps ActionView::Helpers::FormOptionsHelper#grouped_collection_select for form builders:
@@ -844,7 +844,7 @@ module ActionView
#
# Please refer to the documentation of the base helper for details.
def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
- @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options))
+ @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_html_options.merge(html_options))
end
# Wraps ActionView::Helpers::FormOptionsHelper#time_zone_select for form builders:
@@ -856,7 +856,7 @@ module ActionView
#
# Please refer to the documentation of the base helper for details.
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
- @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
+ @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_html_options.merge(html_options))
end
# Wraps ActionView::Helpers::FormOptionsHelper#collection_check_boxes for form builders:
@@ -868,7 +868,7 @@ module ActionView
#
# Please refer to the documentation of the base helper for details.
def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
- @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block)
+ @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options), &block)
end
# Wraps ActionView::Helpers::FormOptionsHelper#collection_radio_buttons for form builders:
@@ -880,7 +880,7 @@ module ActionView
#
# Please refer to the documentation of the base helper for details.
def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
- @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block)
+ @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options), &block)
end
end
end
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index e86e18dd78..ba09738beb 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -22,6 +22,8 @@ module ActionView
mattr_accessor :embed_authenticity_token_in_remote_forms
self.embed_authenticity_token_in_remote_forms = nil
+ mattr_accessor :default_enforce_utf8, default: true
+
# Starts a form tag that points the action to a url configured with <tt>url_for_options</tt> just like
# ActionController::Base#url_for. The method for the form defaults to POST.
#
@@ -387,8 +389,8 @@ module ActionView
# * Any other key creates standard HTML options for the tag.
#
# ==== Examples
- # radio_button_tag 'gender', 'male'
- # # => <input id="gender_male" name="gender" type="radio" value="male" />
+ # radio_button_tag 'favorite_color', 'maroon'
+ # # => <input id="favorite_color_maroon" name="favorite_color" type="radio" value="maroon" />
#
# radio_button_tag 'receive_updates', 'no', true
# # => <input checked="checked" id="receive_updates_no" name="receive_updates" type="radio" value="no" />
@@ -549,7 +551,8 @@ module ActionView
# # => <input src="/assets/save.png" data-confirm="Are you sure?" type="image" />
def image_submit_tag(source, options = {})
options = options.stringify_keys
- tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options)
+ src = path_to_image(source, skip_pipeline: options.delete("skip_pipeline"))
+ tag :input, { "type" => "image", "src" => src }.update(options)
end
# Creates a field set for grouping HTML form elements.
@@ -866,7 +869,7 @@ module ActionView
})
end
- if html_options.delete("enforce_utf8") { true }
+ if html_options.delete("enforce_utf8") { default_enforce_utf8 }
utf8_enforcer_tag + method_tag
else
method_tag
diff --git a/actionview/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb
index dd2cd57ac3..830088bea3 100644
--- a/actionview/lib/action_view/helpers/javascript_helper.rb
+++ b/actionview/lib/action_view/helpers/javascript_helper.rb
@@ -63,6 +63,13 @@ module ActionView
# <%= javascript_tag defer: 'defer' do -%>
# alert('All is good')
# <% end -%>
+ #
+ # If you have a content security policy enabled then you can add an automatic
+ # nonce value by passing <tt>nonce: true</tt> as part of +html_options+. Example:
+ #
+ # <%= javascript_tag nonce: true do -%>
+ # alert('All is good')
+ # <% end -%>
def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
content =
if block_given?
@@ -72,6 +79,10 @@ module ActionView
content_or_options_with_block
end
+ if html_options[:nonce] == true
+ html_options[:nonce] = content_security_policy_nonce
+ end
+
content_tag("script".freeze, javascript_cdata_section(content), html_options)
end
diff --git a/actionview/lib/action_view/helpers/record_tag_helper.rb b/actionview/lib/action_view/helpers/record_tag_helper.rb
deleted file mode 100644
index a6953ee905..0000000000
--- a/actionview/lib/action_view/helpers/record_tag_helper.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-module ActionView
- module Helpers #:nodoc:
- module RecordTagHelper
- def div_for(*) # :nodoc:
- raise NoMethodError, "The `div_for` method has been removed from " \
- "Rails. To continue using it, add the `record_tag_helper` gem to " \
- "your Gemfile:\n" \
- " gem 'record_tag_helper', '~> 1.0'\n" \
- "Consult the Rails upgrade guide for details."
- end
-
- def content_tag_for(*) # :nodoc:
- raise NoMethodError, "The `content_tag_for` method has been removed from " \
- "Rails. To continue using it, add the `record_tag_helper` gem to " \
- "your Gemfile:\n" \
- " gem 'record_tag_helper', '~> 1.0'\n" \
- "Consult the Rails upgrade guide for details."
- end
- end
- end
-end
diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb
index 8e505ab054..1e12aa2736 100644
--- a/actionview/lib/action_view/helpers/rendering_helper.rb
+++ b/actionview/lib/action_view/helpers/rendering_helper.rb
@@ -13,7 +13,6 @@ module ActionView
# * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt>.
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
- # * <tt>:text</tt> - Renders the text passed in out.
# * <tt>:plain</tt> - Renders the text passed in out. Setting the content
# type as <tt>text/plain</tt>.
# * <tt>:html</tt> - Renders the HTML safe string passed in out, otherwise
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index 275a2dffb4..cb0c99c4cf 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -126,7 +126,7 @@ module ActionView
attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
# Vendors the full, link and white list sanitizers.
- # Provided strictly for compatibility and can be removed in Rails 5.1.
+ # Provided strictly for compatibility and can be removed in Rails 6.
def sanitizer_vendor
Rails::Html::Sanitizer
end
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index a6cec3f69c..d12989ea64 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -88,9 +88,10 @@ module ActionView
if value.is_a?(Array)
value = escape ? safe_join(value, " ".freeze) : value.join(" ".freeze)
else
- value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
+ value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s.dup
end
- %(#{key}="#{value.gsub('"'.freeze, '&quot;'.freeze)}")
+ value.gsub!('"'.freeze, "&quot;".freeze)
+ %(#{key}="#{value}")
end
private
@@ -227,10 +228,10 @@ module ActionView
# tag("img", src: "open & shut.png")
# # => <img src="open &amp; shut.png" />
#
- # tag("img", {src: "open &amp; shut.png"}, false, false)
+ # tag("img", { src: "open &amp; shut.png" }, false, false)
# # => <img src="open &amp; shut.png" />
#
- # tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)})
+ # tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
# # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
def tag(name = nil, options = nil, open = false, escape = true)
if name.nil?
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index fed908fcdb..eef527d36f 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -109,11 +109,11 @@ module ActionView
# a little duplication to construct less strings
case
when @object_name.empty?
- "#{sanitized_method_name}#{"[]" if multiple}"
+ "#{sanitized_method_name}#{multiple ? "[]" : ""}"
when index
- "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
+ "#{@object_name}[#{index}][#{sanitized_method_name}]#{multiple ? "[]" : ""}"
else
- "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
+ "#{@object_name}[#{sanitized_method_name}]#{multiple ? "[]" : ""}"
end
end
@@ -170,7 +170,11 @@ module ActionView
option_tags = tag_builder.content_tag_string("option", options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, value: "") + "\n" + option_tags
end
if value.blank? && options[:prompt]
- option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), value: "") + "\n" + option_tags
+ tag_options = { value: "" }.tap do |prompt_opts|
+ prompt_opts[:disabled] = true if options[:disabled] == ""
+ prompt_opts[:selected] = true if options[:selected] == ""
+ end
+ option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
end
option_tags
end
diff --git a/actionview/lib/action_view/helpers/tags/color_field.rb b/actionview/lib/action_view/helpers/tags/color_field.rb
index c5f0bb6bbb..39ab1285c3 100644
--- a/actionview/lib/action_view/helpers/tags/color_field.rb
+++ b/actionview/lib/action_view/helpers/tags/color_field.rb
@@ -15,7 +15,7 @@ module ActionView
def validate_color_string(string)
regex = /#[0-9a-fA-F]{6}/
- if regex.match(string)
+ if regex.match?(string)
string.downcase
else
"#000000"
diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb
index 345484ba92..790721a0b7 100644
--- a/actionview/lib/action_view/helpers/tags/select.rb
+++ b/actionview/lib/action_view/helpers/tags/select.rb
@@ -8,7 +8,7 @@ module ActionView
@choices = block_given? ? template_object.capture { yield || "" } : choices
@choices = @choices.to_a if @choices.is_a?(Range)
- @html_options = html_options.except(:skip_default_ids, :allow_method_names_outside_object)
+ @html_options = html_options
super(object_name, method_name, template_object, options)
end
diff --git a/actionview/lib/action_view/helpers/tags/translator.rb b/actionview/lib/action_view/helpers/tags/translator.rb
index fcf96d2c9c..e81ca3aef0 100644
--- a/actionview/lib/action_view/helpers/tags/translator.rb
+++ b/actionview/lib/action_view/helpers/tags/translator.rb
@@ -16,13 +16,8 @@ module ActionView
translated_attribute || human_attribute_name
end
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :object_name, :method_and_value, :scope, :model
-
private
+ attr_reader :object_name, :method_and_value, :scope, :model
def i18n_default
if model
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index 84d38aa416..77a1c1fed9 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -13,9 +13,9 @@ module ActionView
#
# ==== Sanitization
#
- # Most text helpers by default sanitize the given content, but do not escape it.
- # This means HTML tags will appear in the page but all malicious code will be removed.
- # Let's look at some examples using the +simple_format+ method:
+ # Most text helpers that generate HTML output sanitize the given input by default,
+ # but do not escape it. This means HTML tags will appear in the page but all malicious
+ # code will be removed. Let's look at some examples using the +simple_format+ method:
#
# simple_format('<a href="http://example.com/">Example</a>')
# # => "<p><a href=\"http://example.com/\">Example</a></p>"
@@ -128,7 +128,7 @@ module ActionView
# # => You searched for: <a href="search?q=rails">rails</a>
#
# highlight('<a href="javascript:alert(\'no!\')">ruby</a> on rails', 'rails', sanitize: false)
- # # => "<a>ruby</a> on <mark>rails</mark>"
+ # # => <a href="javascript:alert('no!')">ruby</a> on <mark>rails</mark>
def highlight(text, phrases, options = {})
text = sanitize(text) if options.fetch(:sanitize, true)
@@ -188,7 +188,7 @@ module ActionView
unless separator.empty?
text.split(separator).each do |value|
- if value.match(regex)
+ if value.match?(regex)
phrase = value
break
end
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index 1860bc4732..ba82dcab3e 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -59,11 +59,9 @@ module ActionView
# they can provide HTML values for.
def translate(key, options = {})
options = options.dup
- has_default = options.has_key?(:default)
- remaining_defaults = Array(options.delete(:default)).compact
-
- if has_default && !remaining_defaults.first.kind_of?(Symbol)
- options[:default] = remaining_defaults
+ if options.has_key?(:default)
+ remaining_defaults = Array(options.delete(:default)).compact
+ options[:default] = remaining_defaults unless remaining_defaults.first.kind_of?(Symbol)
end
# If the user has explicitly decided to NOT raise errors, pass that option to I18n.
@@ -85,8 +83,11 @@ module ActionView
end
end
translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
-
- translation.respond_to?(:html_safe) ? translation.html_safe : translation
+ if translation.respond_to?(:map)
+ translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
+ else
+ translation.respond_to?(:html_safe) ? translation.html_safe : translation
+ end
else
I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise))
end
@@ -122,9 +123,12 @@ module ActionView
private
def scope_key_by_partial(key)
- if key.to_s.first == "."
+ stringified_key = key.to_s
+ if stringified_key.first == "."
if @virtual_path
- @virtual_path.gsub(%r{/_?}, ".") + key.to_s
+ @_scope_key_by_partial_cache ||= {}
+ @_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
+ "#{@_scope_key_by_partial_cache[@virtual_path]}#{stringified_key}"
else
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
end
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 889562c478..52bffaab84 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -634,9 +634,9 @@ module ActionView
# suitable for use as the names and values of form input fields:
#
# to_form_params(name: 'David', nationality: 'Danish')
- # # => [{name: :name, value: 'David'}, {name: 'nationality', value: 'Danish'}]
+ # # => [{name: 'name', value: 'David'}, {name: 'nationality', value: 'Danish'}]
#
- # to_form_params(country: {name: 'Denmark'})
+ # to_form_params(country: { name: 'Denmark' })
# # => [{name: 'country[name]', value: 'Denmark'}]
#
# to_form_params(countries: ['Denmark', 'Sweden']})
@@ -666,7 +666,7 @@ module ActionView
params.push(*to_form_params(value, array_prefix))
end
else
- params << { name: namespace, value: attribute.to_param }
+ params << { name: namespace.to_s, value: attribute.to_param }
end
params.sort_by { |pair| pair[:name] }
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index 73dfb267bb..12d06bf376 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -9,6 +9,8 @@ module ActionView
config.action_view = ActiveSupport::OrderedOptions.new
config.action_view.embed_authenticity_token_in_remote_forms = nil
config.action_view.debug_missing_translation = true
+ config.action_view.default_enforce_utf8 = nil
+ config.action_view.finalize_compiled_template_methods = true
config.eager_load_namespaces << ActionView
@@ -35,6 +37,22 @@ module ActionView
end
end
+ initializer "action_view.default_enforce_utf8" do |app|
+ ActiveSupport.on_load(:action_view) do
+ default_enforce_utf8 = app.config.action_view.delete(:default_enforce_utf8)
+ unless default_enforce_utf8.nil?
+ ActionView::Helpers::FormTagHelper.default_enforce_utf8 = default_enforce_utf8
+ end
+ end
+ end
+
+ initializer "action_view.finalize_compiled_template_methods" do |app|
+ ActiveSupport.on_load(:action_view) do
+ ActionView::Template.finalize_compiled_template_methods =
+ app.config.action_view.delete(:finalize_compiled_template_methods)
+ end
+ end
+
initializer "action_view.logger" do
ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
end
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index 5b40af4f2f..d7f97c3b50 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -363,7 +363,7 @@ module ActionView
@options = options
@block = block
- @locals = options[:locals] || {}
+ @locals = options[:locals] ? options[:locals].symbolize_keys : {}
@details = extract_details(options)
prepend_formats(options[:formats])
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 0c4bb73acb..ee1cd61f12 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -9,6 +9,8 @@ module ActionView
class Template
extend ActiveSupport::Autoload
+ mattr_accessor :finalize_compiled_template_methods, default: true
+
# === Encodings in ActionView::Template
#
# ActionView::Template is one of a few sources of potential
@@ -307,7 +309,9 @@ module ActionView
end
mod.module_eval(source, identifier, 0)
- ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
+ if finalize_compiled_template_methods
+ ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
+ end
end
def handle_render_error(view, e)
diff --git a/actionview/package.json b/actionview/package.json
index 787ae06208..624eb5de93 100644
--- a/actionview/package.json
+++ b/actionview/package.json
@@ -1,6 +1,6 @@
{
"name": "rails-ujs",
- "version": "5.2.0-beta2",
+ "version": "6.0.0-alpha",
"description": "Ruby on Rails unobtrusive scripting adapter",
"main": "lib/assets/compiled/rails-ujs.js",
"files": [
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index 8a9d7982d3..3e6b55a87e 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -4,10 +4,6 @@ require "abstract_unit"
require "active_model"
require "controller/fake_models"
-class ApplicationController < ActionController::Base
- self.view_paths = File.join(FIXTURE_LOAD_PATH, "actionpack")
-end
-
module Quiz
# Models
Question = Struct.new(:name, :id) do
@@ -20,7 +16,7 @@ module Quiz
end
# Controller
- class QuestionsController < ApplicationController
+ class QuestionsController < ActionController::Base
def new
render partial: Quiz::Question.new("Namespaced Partial")
end
@@ -28,7 +24,7 @@ module Quiz
end
module Fun
- class GamesController < ApplicationController
+ class GamesController < ActionController::Base
def hello_world; end
def nested_partial_with_form_builder
@@ -37,7 +33,7 @@ module Fun
end
end
-class TestController < ApplicationController
+class TestController < ActionController::Base
protect_from_forgery
before_action :set_variable_for_layout
@@ -489,6 +485,10 @@ class TestController < ApplicationController
render partial: "customer", locals: { customer: Customer.new("david") }
end
+ def partial_with_string_locals
+ render partial: "customer", locals: { "customer" => Customer.new("david") }
+ end
+
def partial_with_form_builder
render partial: ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {})
end
@@ -640,10 +640,15 @@ class RenderTest < ActionController::TestCase
ActionView::Base.logger = ActiveSupport::Logger.new(nil)
@request.host = "www.nextangle.com"
+
+ @old_view_paths = ActionController::Base.view_paths
+ ActionController::Base.view_paths = File.join(FIXTURE_LOAD_PATH, "actionpack")
end
def teardown
ActionView::Base.logger = nil
+
+ ActionController::Base.view_paths = @old_view_paths
end
# :ported:
@@ -1169,6 +1174,11 @@ class RenderTest < ActionController::TestCase
assert_equal "Hello: david", @response.body
end
+ def test_partial_with_string_locals
+ get :partial_with_string_locals
+ assert_equal "Hello: david", @response.body
+ end
+
def test_partial_with_form_builder
get :partial_with_form_builder
assert_equal "<label for=\"post_title\">Title</label>\n", @response.body
diff --git a/actionview/test/activerecord/multifetch_cache_test.rb b/actionview/test/activerecord/multifetch_cache_test.rb
new file mode 100644
index 0000000000..12be069e69
--- /dev/null
+++ b/actionview/test/activerecord/multifetch_cache_test.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require "active_record_unit"
+require "active_record/railties/collection_cache_association_loading"
+
+ActionView::PartialRenderer.prepend(ActiveRecord::Railties::CollectionCacheAssociationLoading)
+
+class MultifetchCacheTest < ActiveRecordTestCase
+ fixtures :topics, :replies
+
+ def setup
+ view_paths = ActionController::Base.view_paths
+
+ @view = Class.new(ActionView::Base) do
+ def view_cache_dependencies
+ []
+ end
+
+ def combined_fragment_cache_key(key)
+ [ :views, key ]
+ end
+ end.new(view_paths, {})
+ end
+
+ def test_only_preloading_for_records_that_miss_the_cache
+ @view.render partial: "test/partial", collection: [topics(:rails)], cached: true
+
+ @topics = Topic.preload(:replies)
+
+ @view.render partial: "test/partial", collection: @topics, cached: true
+
+ assert_not @topics.detect { |topic| topic.id == topics(:rails).id }.replies.loaded?
+ assert @topics.detect { |topic| topic.id != topics(:rails).id }.replies.loaded?
+ end
+end
diff --git a/actionview/test/fixtures/digestor/comments/show.js.erb b/actionview/test/fixtures/digestor/comments/show.js.erb
new file mode 100644
index 0000000000..38b37dfa2b
--- /dev/null
+++ b/actionview/test/fixtures/digestor/comments/show.js.erb
@@ -0,0 +1 @@
+alert("<%=j render("comments/comment") %>")
diff --git a/actionview/test/fixtures/public/.gitignore b/actionview/test/fixtures/public/.gitignore
deleted file mode 100644
index 312e635ee6..0000000000
--- a/actionview/test/fixtures/public/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-absolute/*
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index 284dacf2d4..e68f03d1f4 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -29,6 +29,10 @@ class AssetTagHelperTest < ActionView::TestCase
"http://www.example.com"
end
+ def content_security_policy_nonce
+ "iyhD0Yc0W+c="
+ end
+
AssetPathToTag = {
%(asset_path("")) => %(),
%(asset_path(" ")) => %(),
@@ -407,7 +411,7 @@ class AssetTagHelperTest < ActionView::TestCase
end
def test_javascript_include_tag_is_html_safe
- assert javascript_include_tag("prototype").html_safe?
+ assert_predicate javascript_include_tag("prototype"), :html_safe?
end
def test_javascript_include_tag_relative_protocol
@@ -421,6 +425,10 @@ class AssetTagHelperTest < ActionView::TestCase
assert_dom_equal %(<script src="//assets.example.com/javascripts/prototype.js"></script>), javascript_include_tag("prototype")
end
+ def test_javascript_include_tag_nonce
+ assert_dom_equal %(<script src="/javascripts/bank.js" nonce="iyhD0Yc0W+c="></script>), javascript_include_tag("bank", nonce: true)
+ end
+
def test_stylesheet_path
StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
@@ -460,8 +468,8 @@ class AssetTagHelperTest < ActionView::TestCase
end
def test_stylesheet_link_tag_is_html_safe
- assert stylesheet_link_tag("dir/file").html_safe?
- assert stylesheet_link_tag("dir/other/file", "dir/file2").html_safe?
+ assert_predicate stylesheet_link_tag("dir/file"), :html_safe?
+ assert_predicate stylesheet_link_tag("dir/other/file", "dir/file2"), :html_safe?
end
def test_stylesheet_link_tag_escapes_options
diff --git a/actionview/test/template/atom_feed_helper_test.rb b/actionview/test/template/atom_feed_helper_test.rb
index 1be20dcaae..8e683cb48a 100644
--- a/actionview/test/template/atom_feed_helper_test.rb
+++ b/actionview/test/template/atom_feed_helper_test.rb
@@ -257,7 +257,7 @@ class AtomFeedTest < ActionController::TestCase
get :index, params: { id: "provide_builder" }
# because we pass in the non-default builder, the content generated by the
# helper should go 'nowhere'. Leaving the response body blank.
- assert @response.body.blank?
+ assert_predicate @response.body, :blank?
end
end
diff --git a/actionview/test/template/capture_helper_test.rb b/actionview/test/template/capture_helper_test.rb
index 8a1c00fd00..131e49327e 100644
--- a/actionview/test/template/capture_helper_test.rb
+++ b/actionview/test/template/capture_helper_test.rb
@@ -49,21 +49,21 @@ class CaptureHelperTest < ActionView::TestCase
end
def test_content_for_with_multiple_calls
- assert ! content_for?(:title)
+ assert_not content_for?(:title)
content_for :title, "foo"
content_for :title, "bar"
assert_equal "foobar", content_for(:title)
end
def test_content_for_with_multiple_calls_and_flush
- assert ! content_for?(:title)
+ assert_not content_for?(:title)
content_for :title, "foo"
content_for :title, "bar", flush: true
assert_equal "bar", content_for(:title)
end
def test_content_for_with_block
- assert ! content_for?(:title)
+ assert_not content_for?(:title)
content_for :title do
output_buffer << "foo"
output_buffer << "bar"
@@ -73,7 +73,7 @@ class CaptureHelperTest < ActionView::TestCase
end
def test_content_for_with_block_and_multiple_calls_with_flush
- assert ! content_for?(:title)
+ assert_not content_for?(:title)
content_for :title do
"foo"
end
@@ -84,7 +84,7 @@ class CaptureHelperTest < ActionView::TestCase
end
def test_content_for_with_block_and_multiple_calls_with_flush_nil_content
- assert ! content_for?(:title)
+ assert_not content_for?(:title)
content_for :title do
"foo"
end
@@ -95,7 +95,7 @@ class CaptureHelperTest < ActionView::TestCase
end
def test_content_for_with_block_and_multiple_calls_without_flush
- assert ! content_for?(:title)
+ assert_not content_for?(:title)
content_for :title do
"foo"
end
@@ -106,7 +106,7 @@ class CaptureHelperTest < ActionView::TestCase
end
def test_content_for_with_whitespace_block
- assert ! content_for?(:title)
+ assert_not content_for?(:title)
content_for :title, "foo"
content_for :title do
output_buffer << " \n "
@@ -117,7 +117,7 @@ class CaptureHelperTest < ActionView::TestCase
end
def test_content_for_with_whitespace_block_and_flush
- assert ! content_for?(:title)
+ assert_not content_for?(:title)
content_for :title, "foo"
content_for :title, flush: true do
output_buffer << " \n "
@@ -128,7 +128,7 @@ class CaptureHelperTest < ActionView::TestCase
end
def test_content_for_returns_nil_when_writing
- assert ! content_for?(:title)
+ assert_not content_for?(:title)
assert_nil content_for(:title, "foo")
assert_nil content_for(:title) { output_buffer << "bar"; nil }
assert_nil content_for(:title) { output_buffer << " \n "; nil }
@@ -144,27 +144,27 @@ class CaptureHelperTest < ActionView::TestCase
end
def test_content_for_question_mark
- assert ! content_for?(:title)
+ assert_not content_for?(:title)
content_for :title, "title"
assert content_for?(:title)
- assert ! content_for?(:something_else)
+ assert_not content_for?(:something_else)
end
def test_content_for_should_be_html_safe_after_flush_empty
- assert ! content_for?(:title)
+ assert_not content_for?(:title)
content_for :title do
content_tag(:p, "title")
end
- assert content_for(:title).html_safe?
+ assert_predicate content_for(:title), :html_safe?
content_for :title, "", flush: true
content_for(:title) do
content_tag(:p, "title")
end
- assert content_for(:title).html_safe?
+ assert_predicate content_for(:title), :html_safe?
end
def test_provide
- assert !content_for?(:title)
+ assert_not content_for?(:title)
provide :title, "hi"
assert content_for?(:title)
assert_equal "hi", content_for(:title)
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index 97cfd754be..701e6c86fd 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -141,18 +141,16 @@ class DateHelperTest < ActionView::TestCase
end
def test_distance_in_words_doesnt_use_the_quotient_operator
- rubinius_skip "Date is written in Ruby and relies on Fixnum#/"
- jruby_skip "Date is written in Ruby and relies on Fixnum#/"
+ rubinius_skip "Date is written in Ruby and relies on Integer#/"
+ jruby_skip "Date is written in Ruby and relies on Integer#/"
- klass = RUBY_VERSION > "2.4" ? Integer : Fixnum
-
- # Make sure that we avoid {Integer,Fixnum}#/ (redefined by mathn)
- klass.send :private, :/
+ # Make sure that we avoid Integer#/ (redefined by mathn)
+ Integer.send :private, :/
from = Time.utc(2004, 6, 6, 21, 45, 0)
assert_distance_of_time_in_words(from)
ensure
- klass.send :public, :/
+ Integer.send :public, :/
end
def test_time_ago_in_words_passes_include_seconds
@@ -562,6 +560,15 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_year(Date.current, include_position: true, start_year: 2003, end_year: 2005)
end
+ def test_select_year_with_custom_names
+ year_format_lambda = ->year { "Heisei #{ year - 1988 }" }
+ expected = %(<select id="date_year" name="date[year]">\n).dup
+ expected << %(<option value="2003">Heisei 15</option>\n<option value="2004">Heisei 16</option>\n<option value="2005">Heisei 17</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_year(nil, start_year: 2003, end_year: 2005, year_format: year_format_lambda)
+ end
+
def test_select_hour
expected = %(<select id="date_hour" name="date[hour]">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
@@ -3593,25 +3600,25 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_html_safety
- assert select_day(16).html_safe?
- assert select_month(8).html_safe?
- assert select_year(Time.mktime(2003, 8, 16, 8, 4, 18)).html_safe?
- assert select_minute(Time.mktime(2003, 8, 16, 8, 4, 18)).html_safe?
- assert select_second(Time.mktime(2003, 8, 16, 8, 4, 18)).html_safe?
+ assert_predicate select_day(16), :html_safe?
+ assert_predicate select_month(8), :html_safe?
+ assert_predicate select_year(Time.mktime(2003, 8, 16, 8, 4, 18)), :html_safe?
+ assert_predicate select_minute(Time.mktime(2003, 8, 16, 8, 4, 18)), :html_safe?
+ assert_predicate select_second(Time.mktime(2003, 8, 16, 8, 4, 18)), :html_safe?
- assert select_minute(8, use_hidden: true).html_safe?
- assert select_month(8, prompt: "Choose month").html_safe?
+ assert_predicate select_minute(8, use_hidden: true), :html_safe?
+ assert_predicate select_month(8, prompt: "Choose month"), :html_safe?
- assert select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {}, { class: "selector" }).html_safe?
- assert select_date(Time.mktime(2003, 8, 16), date_separator: " / ", start_year: 2003, end_year: 2005, prefix: "date[first]").html_safe?
+ assert_predicate select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {}, { class: "selector" }), :html_safe?
+ assert_predicate select_date(Time.mktime(2003, 8, 16), date_separator: " / ", start_year: 2003, end_year: 2005, prefix: "date[first]"), :html_safe?
end
def test_object_select_html_safety
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- assert date_select("post", "written_on", default: Time.local(2006, 9, 19, 15, 16, 35), include_blank: true).html_safe?
- assert time_select("post", "written_on", ignore_date: true).html_safe?
+ assert_predicate date_select("post", "written_on", default: Time.local(2006, 9, 19, 15, 16, 35), include_blank: true), :html_safe?
+ assert_predicate time_select("post", "written_on", ignore_date: true), :html_safe?
end
def test_time_tag_with_date
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index 1bfa39a319..ddaa7febb3 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -160,6 +160,18 @@ class TemplateDigestorTest < ActionView::TestCase
assert_equal [:html], tree_template_formats("messages/show").uniq
end
+ def test_template_dependencies_with_fallback_from_js_to_html_format
+ finder.rendered_format = :js
+ assert_equal ["comments/comment"], dependencies("comments/show")
+ end
+
+ def test_template_digest_with_fallback_from_js_to_html_format
+ finder.rendered_format = :js
+ assert_digest_difference("comments/show") do
+ change_template("comments/_comment")
+ end
+ end
+
def test_recursion_in_renders
assert digest("level/recursion") # assert recursion is possible
assert_not_nil digest("level/recursion") # assert digest is stored
diff --git a/actionview/test/template/erb_util_test.rb b/actionview/test/template/erb_util_test.rb
index 8b804105f4..bd702dbe94 100644
--- a/actionview/test/template/erb_util_test.rb
+++ b/actionview/test/template/erb_util_test.rb
@@ -70,24 +70,24 @@ class ErbUtilTest < ActiveSupport::TestCase
def test_json_escape_returns_unsafe_strings_when_passed_unsafe_strings
value = json_escape("asdf")
- assert !value.html_safe?
+ assert_not_predicate value, :html_safe?
end
def test_json_escape_returns_safe_strings_when_passed_safe_strings
value = json_escape("asdf".html_safe)
- assert value.html_safe?
+ assert_predicate value, :html_safe?
end
def test_html_escape_is_html_safe
escaped = h("<p>")
assert_equal "&lt;p&gt;", escaped
- assert escaped.html_safe?
+ assert_predicate escaped, :html_safe?
end
def test_html_escape_passes_html_escape_unmodified
escaped = h("<p>".html_safe)
assert_equal "<p>", escaped
- assert escaped.html_safe?
+ assert_predicate escaped, :html_safe?
end
def test_rest_in_ascii
@@ -104,11 +104,11 @@ class ErbUtilTest < ActiveSupport::TestCase
def test_html_escape_once_returns_unsafe_strings_when_passed_unsafe_strings
value = html_escape_once("1 < 2 &amp; 3")
- assert !value.html_safe?
+ assert_not_predicate value, :html_safe?
end
def test_html_escape_once_returns_safe_strings_when_passed_safe_strings
value = html_escape_once("1 < 2 &amp; 3".html_safe)
- assert value.html_safe?
+ assert_predicate value, :html_safe?
end
end
diff --git a/actionview/test/template/form_helper/form_with_test.rb b/actionview/test/template/form_helper/form_with_test.rb
index 0295ff627d..c30176349c 100644
--- a/actionview/test/template/form_helper/form_with_test.rb
+++ b/actionview/test/template/form_helper/form_with_test.rb
@@ -14,6 +14,16 @@ class FormWithTest < ActionView::TestCase
teardown do
ActionView::Helpers::FormHelper.form_with_generates_ids = @old_value
end
+
+ private
+ def with_default_enforce_utf8(value)
+ old_value = ActionView::Helpers::FormTagHelper.default_enforce_utf8
+ ActionView::Helpers::FormTagHelper.default_enforce_utf8 = value
+
+ yield
+ ensure
+ ActionView::Helpers::FormTagHelper.default_enforce_utf8 = old_value
+ end
end
class FormWithActsLikeFormTagTest < FormWithTest
@@ -108,7 +118,25 @@ class FormWithActsLikeFormTagTest < FormWithTest
actual = form_with(skip_enforcing_utf8: true)
expected = whole_form("http://www.example.com", skip_enforcing_utf8: true)
assert_dom_equal expected, actual
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
+ end
+
+ def test_form_with_default_enforce_utf8_false
+ with_default_enforce_utf8 false do
+ actual = form_with
+ expected = whole_form("http://www.example.com", skip_enforcing_utf8: true)
+ assert_dom_equal expected, actual
+ assert_predicate actual, :html_safe?
+ end
+ end
+
+ def test_form_with_default_enforce_utf8_true
+ with_default_enforce_utf8 true do
+ actual = form_with
+ expected = whole_form("http://www.example.com", skip_enforcing_utf8: false)
+ assert_dom_equal expected, actual
+ assert_predicate actual, :html_safe?
+ end
end
def test_form_with_with_block_in_erb
@@ -190,6 +218,9 @@ class FormWithActsLikeFormForTest < FormWithTest
submit: "Save changes",
another_post: {
update: "Update your %{model}"
+ },
+ "blog/post": {
+ update: "Update your %{model}"
}
}
}
@@ -287,7 +318,8 @@ class FormWithActsLikeFormForTest < FormWithTest
@url_for_options = object
if object.is_a?(Hash) && object[:use_route].blank? && object[:controller].blank?
- object.merge!(controller: "main", action: "index")
+ object[:controller] = "main"
+ object[:action] = "index"
end
super
@@ -433,6 +465,23 @@ class FormWithActsLikeFormForTest < FormWithTest
end
end
+ def test_form_with_with_collection_select
+ post = Post.new
+ def post.active; false; end
+ form_with(model: post) do |f|
+ concat f.collection_select(:active, [true, false], :to_s, :to_s)
+ end
+
+ expected = whole_form("/posts") do
+ "<select name='post[active]' id='post_active'>" \
+ "<option value='true'>true</option>\n" \
+ "<option selected='selected' value='false'>false</option>" \
+ "</select>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_with_with_collection_radio_buttons
post = Post.new
def post.active; false; end
@@ -816,6 +865,34 @@ class FormWithActsLikeFormForTest < FormWithTest
assert_dom_equal expected, output_buffer
end
+ def test_form_with_default_enforce_utf8_true
+ with_default_enforce_utf8 true do
+ form_with(scope: :post) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = whole_form("/", skip_enforcing_utf8: false) do
+ "<input name='post[title]' type='text' value='Hello World' id='post_title' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+ end
+
+ def test_form_with_default_enforce_utf8_false
+ with_default_enforce_utf8 false do
+ form_with(scope: :post) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = whole_form("/", skip_enforcing_utf8: true) do
+ "<input name='post[title]' type='text' value='Hello World' id='post_title' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+ end
+
def test_form_with_without_object
form_with(scope: :post, id: "create-post") do |f|
concat f.text_field(:title)
@@ -962,7 +1039,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
end
- def test_submit_with_object_and_nested_lookup
+ def test_submit_with_object_which_is_overwritten_by_scope_option
with_locale :submit do
form_with(model: @post, scope: :another_post) do |f|
concat f.submit
@@ -976,6 +1053,21 @@ class FormWithActsLikeFormForTest < FormWithTest
end
end
+ def test_submit_with_object_which_is_namespaced
+ blog_post = Blog::Post.new("And his name will be forty and four.", 44)
+ with_locale :submit do
+ form_with(model: blog_post) do |f|
+ concat f.submit
+ end
+
+ expected = whole_form("/posts/44", method: "patch") do
+ "<input name='commit' data-disable-with='Update your Post' type='submit' value='Update your Post' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+ end
+
def test_fields_with_attributes_not_on_model
form_with(model: @post) do |f|
concat f.fields(:comment) { |c|
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 6a317e1a12..38bb1e1d24 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -68,6 +68,9 @@ class FormHelperTest < ActionView::TestCase
submit: "Save changes",
another_post: {
update: "Update your %{model}"
+ },
+ "blog/post": {
+ update: "Update your %{model}"
}
}
}
@@ -165,7 +168,8 @@ class FormHelperTest < ActionView::TestCase
@url_for_options = object
if object.is_a?(Hash) && object[:use_route].blank? && object[:controller].blank?
- object.merge!(controller: "main", action: "index")
+ object[:controller] = "main"
+ object[:action] = "index"
end
super
@@ -612,7 +616,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_check_box_is_html_safe
- assert check_box("post", "secret").html_safe?
+ assert_predicate check_box("post", "secret"), :html_safe?
end
def test_check_box_checked_if_object_value_is_same_that_check_value
@@ -775,7 +779,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_check_box_with_nil_unchecked_value_is_html_safe
- assert check_box("post", "secret", {}, "on", nil).html_safe?
+ assert_predicate check_box("post", "secret", {}, "on", nil), :html_safe?
end
def test_check_box_with_multiple_behavior
@@ -1992,6 +1996,34 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_default_enforce_utf8_false
+ with_default_enforce_utf8 false do
+ form_for(:post) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = whole_form("/", nil, nil, enforce_utf8: false) do
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+ end
+
+ def test_form_for_default_enforce_utf8_true
+ with_default_enforce_utf8 true do
+ form_for(:post) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = whole_form("/", nil, nil, enforce_utf8: true) do
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+ end
+
def test_form_for_with_remote_in_html
form_for(@post, url: "/", html: { remote: true, id: "create-post", method: :patch }) do |f|
concat f.text_field(:title)
@@ -2271,7 +2303,7 @@ class FormHelperTest < ActionView::TestCase
end
end
- def test_submit_with_object_and_nested_lookup
+ def test_submit_with_object_which_is_overwritten_by_as_option
with_locale :submit do
form_for(@post, as: :another_post) do |f|
concat f.submit
@@ -2285,6 +2317,21 @@ class FormHelperTest < ActionView::TestCase
end
end
+ def test_submit_with_object_which_is_namespaced
+ blog_post = Blog::Post.new("And his name will be forty and four.", 44)
+ with_locale :submit do
+ form_for(blog_post) do |f|
+ concat f.submit
+ end
+
+ expected = whole_form("/posts/44", "edit_post_44", "edit_post", method: "patch") do
+ "<input name='commit' data-disable-with='Update your Post' type='submit' value='Update your Post' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+ end
+
def test_nested_fields_for
@comment.body = "Hello World"
form_for(@post) do |f|
@@ -3551,4 +3598,13 @@ class FormHelperTest < ActionView::TestCase
ensure
I18n.locale = old_locale
end
+
+ def with_default_enforce_utf8(value)
+ old_value = ActionView::Helpers::FormTagHelper.default_enforce_utf8
+ ActionView::Helpers::FormTagHelper.default_enforce_utf8 = value
+
+ yield
+ ensure
+ ActionView::Helpers::FormTagHelper.default_enforce_utf8 = old_value
+ end
end
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index 642f450f91..8f796bdb83 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -354,7 +354,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_option_groups_from_collection_for_select_returns_html_safe_string
- assert option_groups_from_collection_for_select(dummy_continents, "countries", "continent_name", "country_id", "country_name", "dk").html_safe?
+ assert_predicate option_groups_from_collection_for_select(dummy_continents, "countries", "continent_name", "country_id", "country_name", "dk"), :html_safe?
end
def test_grouped_options_for_select_with_array
@@ -402,7 +402,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_grouped_options_for_select_returns_html_safe_string
- assert grouped_options_for_select([["Hats", ["Baseball Cap", "Cowboy Hat"]]]).html_safe?
+ assert_predicate grouped_options_for_select([["Hats", ["Baseball Cap", "Cowboy Hat"]]]), :html_safe?
end
def test_grouped_options_for_select_with_prompt_returns_html_escaped_string
@@ -492,7 +492,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_time_zone_options_returns_html_safe_string
- assert time_zone_options_for_select.html_safe?
+ assert_predicate time_zone_options_for_select, :html_safe?
end
def test_select
@@ -511,6 +511,16 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_required_select_with_default_and_selected_placeholder
+ assert_dom_equal(
+ ['<select required="required" name="post[category]" id="post_category"><option disabled="disabled" selected="selected" value="">Choose one</option>',
+ '<option value="lifestyle">lifestyle</option>',
+ '<option value="programming">programming</option>',
+ '<option value="spiritual">spiritual</option></select>'].join("\n"),
+ select(:post, :category, ["lifestyle", "programming", "spiritual"], { selected: "", disabled: "", prompt: "Choose one" }, { required: true })
+ )
+ end
+
def test_select_with_grouped_collection_as_nested_array
@post = Post.new
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index 5e328ebf53..a3500a7eb3 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -142,14 +142,32 @@ class FormTagHelperTest < ActionView::TestCase
actual = form_tag({}, { enforce_utf8: true })
expected = whole_form("http://www.example.com", enforce_utf8: true)
assert_dom_equal expected, actual
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
end
def test_form_tag_enforce_utf8_false
actual = form_tag({}, { enforce_utf8: false })
expected = whole_form("http://www.example.com", enforce_utf8: false)
assert_dom_equal expected, actual
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
+ end
+
+ def test_form_tag_default_enforce_utf8_false
+ with_default_enforce_utf8 false do
+ actual = form_tag({})
+ expected = whole_form("http://www.example.com", enforce_utf8: false)
+ assert_dom_equal expected, actual
+ assert_predicate actual, :html_safe?
+ end
+ end
+
+ def test_form_tag_default_enforce_utf8_true
+ with_default_enforce_utf8 true do
+ actual = form_tag({})
+ expected = whole_form("http://www.example.com", enforce_utf8: true)
+ assert_dom_equal expected, actual
+ assert_predicate actual, :html_safe?
+ end
end
def test_form_tag_with_block_in_erb
@@ -782,4 +800,13 @@ class FormTagHelperTest < ActionView::TestCase
def root_elem(rendered_content)
Nokogiri::HTML::DocumentFragment.parse(rendered_content).children.first # extract from nodeset
end
+
+ def with_default_enforce_utf8(value)
+ old_value = ActionView::Helpers::FormTagHelper.default_enforce_utf8
+ ActionView::Helpers::FormTagHelper.default_enforce_utf8 = value
+
+ yield
+ ensure
+ ActionView::Helpers::FormTagHelper.default_enforce_utf8 = old_value
+ end
end
diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb
index 402ee9b6ae..38469cbe3d 100644
--- a/actionview/test/template/lookup_context_test.rb
+++ b/actionview/test/template/lookup_context_test.rb
@@ -35,7 +35,7 @@ class LookupContextTest < ActiveSupport::TestCase
test "allows me to freeze and retrieve frozen formats" do
@lookup_context.formats.freeze
- assert @lookup_context.formats.frozen?
+ assert_predicate @lookup_context.formats, :frozen?
end
test "provides getters and setters for variants" do
@@ -195,7 +195,7 @@ class LookupContextTest < ActiveSupport::TestCase
assert @lookup_context.cache
template = @lookup_context.disable_cache do
- assert !@lookup_context.cache
+ assert_not @lookup_context.cache
@lookup_context.find("foo", %w(test), true)
end
assert @lookup_context.cache
diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb
index e92bf66203..357ae1326a 100644
--- a/actionview/test/template/number_helper_test.rb
+++ b/actionview/test/template/number_helper_test.rb
@@ -126,43 +126,43 @@ class NumberHelperTest < ActionView::TestCase
end
def test_number_helpers_outputs_are_html_safe
- assert number_to_human(1).html_safe?
- assert !number_to_human("<script></script>").html_safe?
- assert number_to_human("asdf".html_safe).html_safe?
- assert number_to_human("1".html_safe).html_safe?
-
- assert number_to_human_size(1).html_safe?
- assert number_to_human_size(1000000).html_safe?
- assert !number_to_human_size("<script></script>").html_safe?
- assert number_to_human_size("asdf".html_safe).html_safe?
- assert number_to_human_size("1".html_safe).html_safe?
-
- assert number_with_precision(1, strip_insignificant_zeros: false).html_safe?
- assert number_with_precision(1, strip_insignificant_zeros: true).html_safe?
- assert !number_with_precision("<script></script>").html_safe?
- assert number_with_precision("asdf".html_safe).html_safe?
- assert number_with_precision("1".html_safe).html_safe?
-
- assert number_to_currency(1).html_safe?
- assert !number_to_currency("<script></script>").html_safe?
- assert number_to_currency("asdf".html_safe).html_safe?
- assert number_to_currency("1".html_safe).html_safe?
-
- assert number_to_percentage(1).html_safe?
- assert !number_to_percentage("<script></script>").html_safe?
- assert number_to_percentage("asdf".html_safe).html_safe?
- assert number_to_percentage("1".html_safe).html_safe?
-
- assert number_to_phone(1).html_safe?
+ assert_predicate number_to_human(1), :html_safe?
+ assert_not_predicate number_to_human("<script></script>"), :html_safe?
+ assert_predicate number_to_human("asdf".html_safe), :html_safe?
+ assert_predicate number_to_human("1".html_safe), :html_safe?
+
+ assert_predicate number_to_human_size(1), :html_safe?
+ assert_predicate number_to_human_size(1000000), :html_safe?
+ assert_not_predicate number_to_human_size("<script></script>"), :html_safe?
+ assert_predicate number_to_human_size("asdf".html_safe), :html_safe?
+ assert_predicate number_to_human_size("1".html_safe), :html_safe?
+
+ assert_predicate number_with_precision(1, strip_insignificant_zeros: false), :html_safe?
+ assert_predicate number_with_precision(1, strip_insignificant_zeros: true), :html_safe?
+ assert_not_predicate number_with_precision("<script></script>"), :html_safe?
+ assert_predicate number_with_precision("asdf".html_safe), :html_safe?
+ assert_predicate number_with_precision("1".html_safe), :html_safe?
+
+ assert_predicate number_to_currency(1), :html_safe?
+ assert_not_predicate number_to_currency("<script></script>"), :html_safe?
+ assert_predicate number_to_currency("asdf".html_safe), :html_safe?
+ assert_predicate number_to_currency("1".html_safe), :html_safe?
+
+ assert_predicate number_to_percentage(1), :html_safe?
+ assert_not_predicate number_to_percentage("<script></script>"), :html_safe?
+ assert_predicate number_to_percentage("asdf".html_safe), :html_safe?
+ assert_predicate number_to_percentage("1".html_safe), :html_safe?
+
+ assert_predicate number_to_phone(1), :html_safe?
assert_equal "&lt;script&gt;&lt;/script&gt;", number_to_phone("<script></script>")
- assert number_to_phone("<script></script>").html_safe?
- assert number_to_phone("asdf".html_safe).html_safe?
- assert number_to_phone("1".html_safe).html_safe?
-
- assert number_with_delimiter(1).html_safe?
- assert !number_with_delimiter("<script></script>").html_safe?
- assert number_with_delimiter("asdf".html_safe).html_safe?
- assert number_with_delimiter("1".html_safe).html_safe?
+ assert_predicate number_to_phone("<script></script>"), :html_safe?
+ assert_predicate number_to_phone("asdf".html_safe), :html_safe?
+ assert_predicate number_to_phone("1".html_safe), :html_safe?
+
+ assert_predicate number_with_delimiter(1), :html_safe?
+ assert_not_predicate number_with_delimiter("<script></script>"), :html_safe?
+ assert_predicate number_with_delimiter("asdf".html_safe), :html_safe?
+ assert_predicate number_with_delimiter("1".html_safe), :html_safe?
end
def test_number_helpers_should_raise_error_if_invalid_when_specified
diff --git a/actionview/test/template/output_safety_helper_test.rb b/actionview/test/template/output_safety_helper_test.rb
index b5e9a77105..faeeded1c8 100644
--- a/actionview/test/template/output_safety_helper_test.rb
+++ b/actionview/test/template/output_safety_helper_test.rb
@@ -12,7 +12,7 @@ class OutputSafetyHelperTest < ActionView::TestCase
test "raw returns the safe string" do
result = raw(@string)
assert_equal @string, result
- assert result.html_safe?
+ assert_predicate result, :html_safe?
end
test "raw handles nil values correctly" do
@@ -53,11 +53,11 @@ class OutputSafetyHelperTest < ActionView::TestCase
test "to_sentence should escape non-html_safe values" do
actual = to_sentence(%w(< > & ' "))
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
assert_equal("&lt;, &gt;, &amp;, &#39;, and &quot;", actual)
actual = to_sentence(%w(<script>))
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
assert_equal("&lt;script&gt;", actual)
end
@@ -80,19 +80,19 @@ class OutputSafetyHelperTest < ActionView::TestCase
url = "https://example.com"
expected = %(<a href="#{url}">#{url}</a> and <p>&lt;marquee&gt;shady stuff&lt;/marquee&gt;<br /></p>)
actual = to_sentence([link_to(url, url), ptag])
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
assert_equal(expected, actual)
end
test "to_sentence handles blank strings" do
actual = to_sentence(["", "two", "three"])
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
assert_equal ", two, and three", actual
end
test "to_sentence handles nil values" do
actual = to_sentence([nil, "two", "three"])
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
assert_equal ", two, and three", actual
end
diff --git a/actionview/test/template/partial_iteration_test.rb b/actionview/test/template/partial_iteration_test.rb
index 06bbdabac0..1c3c566667 100644
--- a/actionview/test/template/partial_iteration_test.rb
+++ b/actionview/test/template/partial_iteration_test.rb
@@ -18,7 +18,7 @@ class PartialIterationTest < ActiveSupport::TestCase
def test_first_is_false_unless_current_is_at_the_first_index
iteration = ActionView::PartialIteration.new 3
iteration.iterate!
- assert !iteration.first?, "not first when current is 1"
+ assert_not iteration.first?, "not first when current is 1"
end
def test_last_is_true_when_current_is_at_the_last_index
@@ -30,6 +30,6 @@ class PartialIterationTest < ActiveSupport::TestCase
def test_last_is_false_unless_current_is_at_the_last_index
iteration = ActionView::PartialIteration.new 3
- assert !iteration.last?, "not last when current is 0"
+ assert_not iteration.last?, "not last when current is 0"
end
end
diff --git a/actionview/test/template/record_tag_helper_test.rb b/actionview/test/template/record_tag_helper_test.rb
deleted file mode 100644
index 7bbbfccdd0..0000000000
--- a/actionview/test/template/record_tag_helper_test.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-require "abstract_unit"
-
-class RecordTagPost
- extend ActiveModel::Naming
-
- attr_accessor :id, :body
-
- def initialize
- @id = 45
- @body = "What a wonderful world!"
-
- yield self if block_given?
- end
-end
-
-class RecordTagHelperTest < ActionView::TestCase
- tests ActionView::Helpers::RecordTagHelper
-
- def setup
- super
- @post = RecordTagPost.new
- end
-
- def test_content_tag_for
- assert_raises(NoMethodError) { content_tag_for(:li, @post) }
- end
-
- def test_div_for
- assert_raises(NoMethodError) { div_for(@post, class: "special") }
- end
-end
diff --git a/actionview/test/template/sanitize_helper_test.rb b/actionview/test/template/sanitize_helper_test.rb
index 0e690c82cb..181f09ab65 100644
--- a/actionview/test/template/sanitize_helper_test.rb
+++ b/actionview/test/template/sanitize_helper_test.rb
@@ -38,6 +38,6 @@ class SanitizeHelperTest < ActionView::TestCase
end
def test_sanitize_is_marked_safe
- assert sanitize("<html><script></script></html>").html_safe?
+ assert_predicate sanitize("<html><script></script></html>"), :html_safe?
end
end
diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb
index a746b9c1b5..9a6226fd04 100644
--- a/actionview/test/template/tag_helper_test.rb
+++ b/actionview/test/template/tag_helper_test.rb
@@ -81,7 +81,7 @@ class TagHelperTest < ActionView::TestCase
def test_content_tag
assert_equal "<a href=\"create\">Create</a>", content_tag("a", "Create", "href" => "create")
- assert content_tag("a", "Create", "href" => "create").html_safe?
+ assert_predicate content_tag("a", "Create", "href" => "create"), :html_safe?
assert_equal content_tag("a", "Create", "href" => "create"),
content_tag("a", "Create", href: "create")
assert_equal "<p>&lt;script&gt;evil_js&lt;/script&gt;</p>",
@@ -92,7 +92,7 @@ class TagHelperTest < ActionView::TestCase
def test_tag_builder_with_content
assert_equal "<div id=\"post_1\">Content</div>", tag.div("Content", id: "post_1")
- assert tag.div("Content", id: "post_1").html_safe?
+ assert_predicate tag.div("Content", id: "post_1"), :html_safe?
assert_equal tag.div("Content", id: "post_1"),
tag.div("Content", "id": "post_1")
assert_equal "<p>&lt;script&gt;evil_js&lt;/script&gt;</p>",
diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb
index 05e5f21ce4..d98fd4f9a2 100644
--- a/actionview/test/template/test_case_test.rb
+++ b/actionview/test/template/test_case_test.rb
@@ -192,7 +192,7 @@ module ActionView
helper HelperThatInvokesProtectAgainstForgery
test "protect_from_forgery? in any helpers returns false" do
- assert !view.help_me
+ assert_not view.help_me
end
end
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index f247de066f..45edfe18be 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -19,12 +19,12 @@ class TextHelperTest < ActionView::TestCase
end
def test_simple_format_should_be_html_safe
- assert simple_format("<b> test with html tags </b>").html_safe?
+ assert_predicate simple_format("<b> test with html tags </b>"), :html_safe?
end
def test_simple_format_included_in_isolation
helper_klass = Class.new { include ActionView::Helpers::TextHelper }
- assert helper_klass.new.simple_format("<b> test with html tags </b>").html_safe?
+ assert_predicate helper_klass.new.simple_format("<b> test with html tags </b>"), :html_safe?
end
def test_simple_format
@@ -123,7 +123,7 @@ class TextHelperTest < ActionView::TestCase
end
def test_truncate_should_be_html_safe
- assert truncate("Hello World!", length: 12).html_safe?
+ assert_predicate truncate("Hello World!", length: 12), :html_safe?
end
def test_truncate_should_escape_the_input
@@ -136,12 +136,12 @@ class TextHelperTest < ActionView::TestCase
def test_truncate_with_escape_false_should_be_html_safe
truncated = truncate("Hello <script>code!</script>World!!", length: 12, escape: false)
- assert truncated.html_safe?
+ assert_predicate truncated, :html_safe?
end
def test_truncate_with_block_should_be_html_safe
truncated = truncate("Here's a long test and I need a continue to read link", length: 27) { link_to "Continue", "#" }
- assert truncated.html_safe?
+ assert_predicate truncated, :html_safe?
end
def test_truncate_with_block_should_escape_the_input
@@ -156,7 +156,7 @@ class TextHelperTest < ActionView::TestCase
def test_truncate_with_block_with_escape_false_should_be_html_safe
truncated = truncate("<script>code!</script>Here's a long test and I need a continue to read link", length: 27, escape: false) { link_to "Continue", "#" }
- assert truncated.html_safe?
+ assert_predicate truncated, :html_safe?
end
def test_truncate_with_block_should_escape_the_block
@@ -165,7 +165,7 @@ class TextHelperTest < ActionView::TestCase
end
def test_highlight_should_be_html_safe
- assert highlight("This is a beautiful morning", "beautiful").html_safe?
+ assert_predicate highlight("This is a beautiful morning", "beautiful"), :html_safe?
end
def test_highlight
@@ -297,7 +297,7 @@ class TextHelperTest < ActionView::TestCase
end
def test_excerpt_should_not_be_html_safe
- assert !excerpt("This is a beautiful! morning", "beautiful", radius: 5).html_safe?
+ assert_not_predicate excerpt("This is a beautiful! morning", "beautiful", radius: 5), :html_safe?
end
def test_excerpt_in_borderline_cases
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index 8956a584ff..e756348938 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -75,7 +75,7 @@ class TranslationHelperTest < ActiveSupport::TestCase
def test_returns_missing_translation_message_with_unescaped_interpolation
expected = '<span class="translation_missing" title="translation missing: en.translations.missing, name: Kir, year: 2015, vulnerable: &amp;quot; onclick=&amp;quot;alert()&amp;quot;">Missing</span>'
assert_equal expected, translate(:"translations.missing", name: "Kir", year: "2015", vulnerable: %{" onclick="alert()"})
- assert translate(:"translations.missing").html_safe?
+ assert_predicate translate(:"translations.missing"), :html_safe?
end
def test_returns_missing_translation_message_does_filters_out_i18n_options
@@ -145,11 +145,11 @@ class TranslationHelperTest < ActiveSupport::TestCase
end
def test_translate_marks_translations_named_html_as_safe_html
- assert translate(:'translations.html').html_safe?
+ assert_predicate translate(:'translations.html'), :html_safe?
end
def test_translate_marks_translations_with_a_html_suffix_as_safe_html
- assert translate(:'translations.hello_html').html_safe?
+ assert_predicate translate(:'translations.hello_html'), :html_safe?
end
def test_translate_escapes_interpolations_in_translations_with_a_html_suffix
@@ -164,8 +164,11 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert_equal "<a>Other &lt;One&gt;</a>", translate(:'translations.count_html', count: "<One>")
end
- def test_translation_returning_an_array_ignores_html_suffix
- assert_equal ["foo", "bar"], translate(:'translations.array_html')
+ def test_translate_marks_array_of_translations_with_a_html_safe_suffix_as_safe_html
+ translate(:'translations.array_html').tap do |translated|
+ assert_equal %w( foo bar ), translated
+ assert translated.all?(&:html_safe?)
+ end
end
def test_translate_with_default_named_html
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 0cd0386cac..9d91dbb72b 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -77,11 +77,18 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_to_form_params_with_hash
assert_equal(
- [{ name: :name, value: "David" }, { name: :nationality, value: "Danish" }],
+ [{ name: "name", value: "David" }, { name: "nationality", value: "Danish" }],
to_form_params(name: "David", nationality: "Danish")
)
end
+ def test_to_form_params_with_hash_having_symbol_and_string_keys
+ assert_equal(
+ [{ name: "name", value: "David" }, { name: "nationality", value: "Danish" }],
+ to_form_params("name" => "David", :nationality => "Danish")
+ )
+ end
+
def test_to_form_params_with_nested_hash
assert_equal(
[{ name: "country[name]", value: "Denmark" }],
@@ -508,16 +515,16 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_current_page_considering_params
@request = request_for_url("/?order=desc&page=1")
- assert !current_page?(url_hash, check_parameters: true)
- assert !current_page?(url_hash.merge(check_parameters: true))
- assert !current_page?(ActionController::Parameters.new(url_hash.merge(check_parameters: true)).permit!)
- assert !current_page?("http://www.example.com/", check_parameters: true)
+ assert_not current_page?(url_hash, check_parameters: true)
+ assert_not current_page?(url_hash.merge(check_parameters: true))
+ assert_not current_page?(ActionController::Parameters.new(url_hash.merge(check_parameters: true)).permit!)
+ assert_not current_page?("http://www.example.com/", check_parameters: true)
end
def test_current_page_considering_params_when_options_does_not_respond_to_to_hash
@request = request_for_url("/?order=desc&page=1")
- assert !current_page?(:back, check_parameters: false)
+ assert_not current_page?(:back, check_parameters: false)
end
def test_current_page_with_params_that_match
@@ -562,7 +569,7 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_current_page_with_not_get_verb
@request = request_for_url("/events", method: :post)
- assert !current_page?("/events")
+ assert_not current_page?("/events")
end
def test_link_unless_current
@@ -663,7 +670,7 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def test_mail_to_returns_html_safe_string
- assert mail_to("david@loudthinking.com").html_safe?
+ assert_predicate mail_to("david@loudthinking.com"), :html_safe?
end
def test_mail_to_with_block
diff --git a/actionview/test/tmp/.keep b/actionview/test/tmp/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/actionview/test/tmp/.keep
+++ /dev/null
diff --git a/actionview/test/ujs/.gitignore b/actionview/test/ujs/.gitignore
deleted file mode 100644
index 31dbbff57c..0000000000
--- a/actionview/test/ujs/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/log
diff --git a/actionview/test/ujs/public/test/call-ajax.js b/actionview/test/ujs/public/test/call-ajax.js
index 49e64cad5c..4d0bfb0806 100644
--- a/actionview/test/ujs/public/test/call-ajax.js
+++ b/actionview/test/ujs/public/test/call-ajax.js
@@ -8,7 +8,6 @@ module('call-ajax', {
})
asyncTest('call ajax without "ajax:beforeSend"', 1, function() {
-
var link = $('#qunit-fixture a')
link.bindNative('click', function() {
Rails.ajax({
@@ -21,7 +20,7 @@ asyncTest('call ajax without "ajax:beforeSend"', 1, function() {
})
link.triggerNative('click')
- setTimeout(function() { start() }, 13)
+ setTimeout(function() { start() }, 50)
})
})()
diff --git a/actionview/test/ujs/public/test/call-remote-callbacks.js b/actionview/test/ujs/public/test/call-remote-callbacks.js
index 48763f6301..9c0c8cfb4b 100644
--- a/actionview/test/ujs/public/test/call-remote-callbacks.js
+++ b/actionview/test/ujs/public/test/call-remote-callbacks.js
@@ -75,9 +75,9 @@ asyncTest('setting data("with-credentials",true) with "ajax:before" uses new set
asyncTest('stopping the "ajax:beforeSend" event aborts the request', 1, function() {
submit(function(form) {
- form.bindNative('ajax:beforeSend', function() {
+ form.bindNative('ajax:beforeSend', function(e) {
ok(true, 'aborting request in ajax:beforeSend')
- return false
+ e.preventDefault()
})
form.unbind('ajax:send').bindNative('ajax:send', function() {
ok(false, 'ajax:send should not run')
@@ -148,8 +148,8 @@ function skipIt() {
.bind('iframe:loading', function() {
ok(false, 'form should not get submitted')
})
- .bindNative('ajax:aborted:file', function() {
- return false
+ .bindNative('ajax:aborted:file', function(e) {
+ e.preventDefault()
})
.triggerNative('submit')
@@ -162,9 +162,9 @@ function skipIt() {
}
asyncTest('"ajax:beforeSend" can be observed and stopped with event delegation', 1, function() {
- $(document).delegate('form[data-remote]', 'ajax:beforeSend', function() {
+ $(document).delegate('form[data-remote]', 'ajax:beforeSend', function(e) {
ok(true, 'ajax:beforeSend observed with event delegation')
- return false
+ e.preventDefault()
})
submit(function(form) {
diff --git a/actionview/test/ujs/public/test/call-remote.js b/actionview/test/ujs/public/test/call-remote.js
index 5932195363..778dc1b09a 100644
--- a/actionview/test/ujs/public/test/call-remote.js
+++ b/actionview/test/ujs/public/test/call-remote.js
@@ -128,6 +128,17 @@ asyncTest('execution of JS code does not modify current DOM', 1, function() {
})
})
+asyncTest('HTML content should be plain-text', 1, function() {
+ buildForm({ method: 'post', 'data-type': 'html' })
+
+ $('form').append('<input type="text" name="content_type" value="text/html">')
+ $('form').append('<input type="text" name="content" value="<p>hello</p>">')
+
+ submit(function(e, data, status, xhr) {
+ ok(data === '<p>hello</p>', 'returned data should be a plain-text string')
+ })
+})
+
asyncTest('XML document should be parsed', 1, function() {
buildForm({ method: 'post', 'data-type': 'html' })
@@ -199,7 +210,7 @@ asyncTest('allow empty form "action"', 1, function() {
buildForm({ action: '' })
$('#qunit-fixture').find('form')
- .bindNative('ajax:beforeSend', function(e, xhr, settings) {
+ .bindNative('ajax:beforeSend', function(evt, xhr, settings) {
// Get current location (the same way jQuery does)
try {
currentLocation = location.href
@@ -218,7 +229,7 @@ asyncTest('allow empty form "action"', 1, function() {
// Prevent the request from actually getting sent to the current page and
// causing an error.
- return false
+ evt.preventDefault()
})
.triggerNative('submit')
@@ -246,7 +257,7 @@ asyncTest('intelligently guesses crossDomain behavior when target URL has a diff
equal(settings.crossDomain, true, 'crossDomain should be set to true')
// prevent request from actually getting sent off-domain
- return false
+ evt.preventDefault()
})
.triggerNative('submit')
@@ -265,7 +276,7 @@ asyncTest('intelligently guesses crossDomain behavior when target URL consists o
equal(settings.crossDomain, false, 'crossDomain should be set to false')
// prevent request from actually getting sent off-domain
- return false
+ evt.preventDefault()
})
.triggerNative('submit')
diff --git a/actionview/test/ujs/public/test/data-confirm.js b/actionview/test/ujs/public/test/data-confirm.js
index d1ea82ea7e..1bd57b69ad 100644
--- a/actionview/test/ujs/public/test/data-confirm.js
+++ b/actionview/test/ujs/public/test/data-confirm.js
@@ -173,9 +173,9 @@ asyncTest('binding to confirm event of a link and returning false', 1, function(
}
$('a[data-confirm]')
- .bindNative('confirm', function() {
+ .bindNative('confirm', function(e) {
App.assertCallbackInvoked('confirm')
- return false
+ e.preventDefault()
})
.bindNative('confirm:complete', function() {
App.assertCallbackNotInvoked('confirm:complete')
@@ -194,9 +194,9 @@ asyncTest('binding to confirm event of a button and returning false', 1, functio
}
$('button[data-confirm]')
- .bindNative('confirm', function() {
+ .bindNative('confirm', function(e) {
App.assertCallbackInvoked('confirm')
- return false
+ e.preventDefault()
})
.bindNative('confirm:complete', function() {
App.assertCallbackNotInvoked('confirm:complete')
@@ -216,9 +216,9 @@ asyncTest('binding to confirm:complete event of a link and returning false', 2,
}
$('a[data-confirm]')
- .bindNative('confirm:complete', function() {
+ .bindNative('confirm:complete', function(e) {
App.assertCallbackInvoked('confirm:complete')
- return false
+ e.preventDefault()
})
.bindNative('ajax:beforeSend', function() {
App.assertCallbackNotInvoked('ajax:beforeSend')
@@ -238,9 +238,9 @@ asyncTest('binding to confirm:complete event of a button and returning false', 2
}
$('button[data-confirm]')
- .bindNative('confirm:complete', function() {
+ .bindNative('confirm:complete', function(e) {
App.assertCallbackInvoked('confirm:complete')
- return false
+ e.preventDefault()
})
.bindNative('ajax:beforeSend', function() {
App.assertCallbackNotInvoked('ajax:beforeSend')
@@ -314,3 +314,29 @@ asyncTest('clicking on the children of a disabled button should not trigger a co
start()
}, 50)
})
+
+asyncTest('clicking on a link with data-confirm attribute with custom confirm handler. Confirm yes.', 7, function() {
+ var message, element
+ // redefine confirm function so we can make sure it's not called
+ window.confirm = function(msg) {
+ ok(false, 'confirm dialog should not be called')
+ }
+ // custom auto-confirm:
+ Rails.confirm = function(msg, elem) { message = msg; element = elem; return true }
+
+ $('a[data-confirm]')
+ .bindNative('confirm:complete', function(e, data) {
+ App.assertCallbackInvoked('confirm:complete')
+ ok(data == true, 'confirm:complete passes in confirm answer (true)')
+ })
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ App.assertCallbackInvoked('ajax:success')
+ App.assertRequestPath(data, '/echo')
+ App.assertGetRequest(data)
+
+ equal(message, 'Are you absolutely sure?')
+ equal(element, $('a[data-confirm]').get(0))
+ start()
+ })
+ .triggerNative('click')
+})
diff --git a/actionview/test/ujs/public/test/data-disable-with.js b/actionview/test/ujs/public/test/data-disable-with.js
index b29cbbc867..645ad494c3 100644
--- a/actionview/test/ujs/public/test/data-disable-with.js
+++ b/actionview/test/ujs/public/test/data-disable-with.js
@@ -132,7 +132,8 @@ test('form input[type=submit][data-disable-with] re-enables when `pageshow` even
})
asyncTest('form[data-remote] input[type=submit][data-disable-with] is replaced in ajax callback', 2, function() {
- var form = $('form:not([data-remote])').attr('data-remote', 'true'), origFormContents = form.html()
+ var form = $('#qunit-fixture form:not([data-remote])').attr('data-remote', 'true'),
+ origFormContents = form.html()
form.bindNative('ajax:success', function() {
form.html(origFormContents)
@@ -146,7 +147,8 @@ asyncTest('form[data-remote] input[type=submit][data-disable-with] is replaced i
})
asyncTest('form[data-remote] input[data-disable-with] is replaced with disabled field in ajax callback', 2, function() {
- var form = $('form:not([data-remote])').attr('data-remote', 'true'), input = form.find('input[type=submit]'),
+ var form = $('#qunit-fixture form:not([data-remote])').attr('data-remote', 'true'),
+ input = form.find('input[type=submit]'),
newDisabledInput = input.clone().attr('disabled', 'disabled')
form.bindNative('ajax:success', function() {
@@ -238,9 +240,9 @@ asyncTest('a[data-remote][data-disable-with] re-enables when `ajax:before` event
App.checkEnabledState(link, 'Click me')
link
- .bindNative('ajax:before', function() {
+ .bindNative('ajax:before', function(e) {
App.checkDisabledState(link, 'clicking...')
- return false
+ e.preventDefault()
})
.triggerNative('click')
@@ -256,9 +258,9 @@ asyncTest('a[data-remote][data-disable-with] re-enables when `ajax:beforeSend` e
App.checkEnabledState(link, 'Click me')
link
- .bindNative('ajax:beforeSend', function() {
+ .bindNative('ajax:beforeSend', function(e) {
App.checkDisabledState(link, 'clicking...')
- return false
+ e.preventDefault()
})
.triggerNative('click')
@@ -293,8 +295,9 @@ asyncTest('form[data-remote] input|button|textarea[data-disable-with] does not d
submit = $('<input type="submit" data-disable-with="submitting ..." name="submit2" value="Submit" />').appendTo(form)
form
- .bindNative('ajax:beforeSend', function() {
- return false
+ .bindNative('ajax:beforeSend', function(e) {
+ e.preventDefault()
+ e.stopPropagation()
})
.triggerNative('submit')
@@ -343,9 +346,9 @@ asyncTest('button[data-remote][data-disable-with] re-enables when `ajax:before`
App.checkEnabledState(button, 'Click me')
button
- .bindNative('ajax:before', function() {
+ .bindNative('ajax:before', function(e) {
App.checkDisabledState(button, 'clicking...')
- return false
+ e.preventDefault()
})
.triggerNative('click')
@@ -361,9 +364,9 @@ asyncTest('button[data-remote][data-disable-with] re-enables when `ajax:beforeSe
App.checkEnabledState(button, 'Click me')
button
- .bindNative('ajax:beforeSend', function() {
+ .bindNative('ajax:beforeSend', function(e) {
App.checkDisabledState(button, 'clicking...')
- return false
+ e.preventDefault()
})
.triggerNative('click')
diff --git a/actionview/test/ujs/public/test/data-disable.js b/actionview/test/ujs/public/test/data-disable.js
index ccc38cf9ae..e9919764b6 100644
--- a/actionview/test/ujs/public/test/data-disable.js
+++ b/actionview/test/ujs/public/test/data-disable.js
@@ -91,7 +91,7 @@ asyncTest('form input[type=submit][data-disable] disables', 6, function() {
})
asyncTest('form[data-remote] input[type=submit][data-disable] is replaced in ajax callback', 2, function() {
- var form = $('form:not([data-remote])').attr('data-remote', 'true'), origFormContents = form.html()
+ var form = $('#qunit-fixture form:not([data-remote])').attr('data-remote', 'true'), origFormContents = form.html()
form.bindNative('ajax:success', function() {
form.html(origFormContents)
@@ -105,7 +105,7 @@ asyncTest('form[data-remote] input[type=submit][data-disable] is replaced in aja
})
asyncTest('form[data-remote] input[data-disable] is replaced with disabled field in ajax callback', 2, function() {
- var form = $('form:not([data-remote])').attr('data-remote', 'true'), input = form.find('input[type=submit]'),
+ var form = $('#qunit-fixture form:not([data-remote])').attr('data-remote', 'true'), input = form.find('input[type=submit]'),
newDisabledInput = input.clone().attr('disabled', 'disabled')
form.bindNative('ajax:success', function() {
@@ -168,9 +168,9 @@ asyncTest('a[data-remote][data-disable] re-enables when `ajax:before` event is c
App.checkEnabledState(link, 'Click me')
link
- .bindNative('ajax:before', function() {
+ .bindNative('ajax:before', function(e) {
App.checkDisabledState(link, 'Click me')
- return false
+ e.preventDefault()
})
.triggerNative('click')
@@ -186,9 +186,9 @@ asyncTest('a[data-remote][data-disable] re-enables when `ajax:beforeSend` event
App.checkEnabledState(link, 'Click me')
link
- .bindNative('ajax:beforeSend', function() {
+ .bindNative('ajax:beforeSend', function(e) {
App.checkDisabledState(link, 'Click me')
- return false
+ e.preventDefault()
})
.triggerNative('click')
@@ -223,8 +223,9 @@ asyncTest('form[data-remote] input|button|textarea[data-disable] does not disabl
submit = $('<input type="submit" data-disable="submitting ..." name="submit2" value="Submit" />').appendTo(form)
form
- .bindNative('ajax:beforeSend', function() {
- return false
+ .bindNative('ajax:beforeSend', function(e) {
+ e.preventDefault()
+ e.stopPropagation()
})
.triggerNative('submit')
@@ -273,9 +274,9 @@ asyncTest('button[data-remote][data-disable] re-enables when `ajax:before` event
App.checkEnabledState(button, 'Click me')
button
- .bindNative('ajax:before', function() {
+ .bindNative('ajax:before', function(e) {
App.checkDisabledState(button, 'Click me')
- return false
+ e.preventDefault()
})
.triggerNative('click')
@@ -291,9 +292,9 @@ asyncTest('button[data-remote][data-disable] re-enables when `ajax:beforeSend` e
App.checkEnabledState(button, 'Click me')
button
- .bindNative('ajax:beforeSend', function() {
+ .bindNative('ajax:beforeSend', function(e) {
App.checkDisabledState(button, 'Click me')
- return false
+ e.preventDefault()
})
.triggerNative('click')
diff --git a/actionview/test/ujs/public/test/data-remote.js b/actionview/test/ujs/public/test/data-remote.js
index cbbd4e6c92..3503c2cff3 100644
--- a/actionview/test/ujs/public/test/data-remote.js
+++ b/actionview/test/ujs/public/test/data-remote.js
@@ -272,9 +272,10 @@ asyncTest('returning false in form\'s submit bindings in non-submit-bubbling bro
form
.append($('<input type="submit" />'))
- .bindNative('submit', function() {
+ .bindNative('submit', function(e) {
ok(true, 'binding handler is called')
- return false
+ e.preventDefault()
+ e.stopPropagation()
})
.bindNative('ajax:beforeSend', function() {
ok(false, 'form should not be submitted')
@@ -296,8 +297,8 @@ asyncTest('clicking on a link with falsy "data-remote" attribute does not fire a
.bindNative('ajax:beforeSend', function() {
ok(false, 'ajax should not be triggered')
})
- .bindNative('click', function() {
- return false
+ .bindNative('click', function(e) {
+ e.preventDefault()
})
.triggerNative('click')
@@ -314,8 +315,8 @@ asyncTest('ctrl-clicking on a link with falsy "data-remote" attribute does not f
.bindNative('ajax:beforeSend', function() {
ok(false, 'ajax should not be triggered')
})
- .bindNative('click', function() {
- return false
+ .bindNative('click', function(e) {
+ e.preventDefault()
})
.triggerNative('click', { metaKey: true })
@@ -333,8 +334,8 @@ asyncTest('clicking on a button with falsy "data-remote" attribute', 0, function
.bindNative('ajax:beforeSend', function() {
ok(false, 'ajax should not be triggered')
})
- .bindNative('click', function() {
- return false
+ .bindNative('click', function(e) {
+ e.preventDefault()
})
.triggerNative('click')
@@ -347,8 +348,8 @@ asyncTest('submitting a form with falsy "data-remote" attribute', 0, function()
.bindNative('ajax:beforeSend', function() {
ok(false, 'ajax should not be triggered')
})
- .bindNative('submit', function() {
- return false
+ .bindNative('submit', function(e) {
+ e.preventDefault()
})
.triggerNative('submit')
@@ -429,7 +430,7 @@ asyncTest('changing a select option without "data-url" attribute still fires aja
ajaxLocation = settings.url.replace(settings.data, '').replace(/&$/, '').replace(/\?$/, '')
equal(ajaxLocation, currentLocation, 'URL should be current page by default')
- return false
+ e.preventDefault()
})
.val('optionValue2')
.triggerNative('change')
diff --git a/actionview/test/ujs/public/test/override.js b/actionview/test/ujs/public/test/override.js
index 299c7018cc..d73276ee4f 100644
--- a/actionview/test/ujs/public/test/override.js
+++ b/actionview/test/ujs/public/test/override.js
@@ -25,7 +25,7 @@ asyncTest('the getter for an element\'s href is overridable', 1, function() {
$('#qunit-fixture a')
.bindNative('ajax:beforeSend', function(e, xhr, options) {
equal('/data/href', options.url)
- return false
+ e.preventDefault()
})
.triggerNative('click')
start()
@@ -35,7 +35,7 @@ asyncTest('the getter for an element\'s href works normally if not overridden',
$('#qunit-fixture a')
.bindNative('ajax:beforeSend', function(e, xhr, options) {
equal(location.protocol + '//' + location.host + '/real/href', options.url)
- return false
+ e.preventDefault()
})
.triggerNative('click')
start()
diff --git a/actionview/test/ujs/public/test/settings.js b/actionview/test/ujs/public/test/settings.js
index 299c71bb00..b1ce3b8c64 100644
--- a/actionview/test/ujs/public/test/settings.js
+++ b/actionview/test/ujs/public/test/settings.js
@@ -103,14 +103,16 @@ $.fn.extend({
bindNative: function(event, handler) {
if (!handler) return this
- this.bind(event, function(e) {
+ var el = this[0]
+ el.addEventListener(event, function(e) {
var args = []
- if (e.originalEvent.detail) {
- args = e.originalEvent.detail.slice()
+ if (e.detail) {
+ args = e.detail.slice()
}
args.unshift(e)
- return handler.apply(this, args)
- })
+ return handler.apply(el, args)
+ }, false)
+
return this
}
})
diff --git a/actionview/test/ujs/server.rb b/actionview/test/ujs/server.rb
index 7d1bab4b2a..48e9bcb65f 100644
--- a/actionview/test/ujs/server.rb
+++ b/actionview/test/ujs/server.rb
@@ -23,18 +23,30 @@ module UJS
config.public_file_server.enabled = true
config.logger = Logger.new(STDOUT)
config.log_level = :error
+
+ config.content_security_policy do |policy|
+ policy.default_src :self, :https
+ policy.font_src :self, :https, :data
+ policy.img_src :self, :https, :data
+ policy.object_src :none
+ policy.script_src :self, :https
+ policy.style_src :self, :https
+ end
+
+ config.content_security_policy_nonce_generator = ->(req) { SecureRandom.base64(16) }
end
end
module TestsHelper
def test_to(*names)
- names = ["/vendor/qunit.js", "settings"] + names
- names.map { |name| script_tag name }.join("\n").html_safe
- end
+ names = names.map { |name| "/test/#{name}.js" }
+ names = %w[/vendor/qunit.js /test/settings.js] + names
- def script_tag(src)
- src = "/test/#{src}.js" unless src.index("/")
- %(<script src="#{src}" type="text/javascript"></script>).html_safe
+ capture do
+ names.each do |name|
+ concat(javascript_include_tag(name))
+ end
+ end
end
end
@@ -56,7 +68,7 @@ class TestsController < ActionController::Base
elsif params[:iframe]
payload = JSON.generate(data).gsub("<", "&lt;").gsub(">", "&gt;")
html = <<-HTML
- <script>
+ <script nonce="#{request.content_security_policy_nonce}">
if (window.top && window.top !== window)
window.top.jQuery.event.trigger('iframe:loaded', #{payload})
</script>
diff --git a/actionview/test/ujs/views/layouts/application.html.erb b/actionview/test/ujs/views/layouts/application.html.erb
index c787e77b84..8f6f6fc17f 100644
--- a/actionview/test/ujs/views/layouts/application.html.erb
+++ b/actionview/test/ujs/views/layouts/application.html.erb
@@ -2,9 +2,10 @@
<html id="html">
<head>
<title><%= @title %></title>
+ <%= csp_meta_tag %>
<link href="/vendor/qunit.css" media="screen" rel="stylesheet" type="text/css" media="screen, projection" />
<script src="/vendor/jquery-2.2.0.js" type="text/javascript"></script>
- <script>
+ <%= javascript_tag nonce: true do %>
// This is for test in override.js.
// Must go before rails-ujs.
document.addEventListener('rails:attachBindings', function() {
@@ -15,8 +16,8 @@
e.preventDefault();
});
});
- </script>
- <%= script_tag "/rails-ujs.js" %>
+ <% end %>
+ <%= javascript_include_tag "/rails-ujs.js" %>
</head>
<body id="body">