diff options
Diffstat (limited to 'actionview')
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 👋 🙂" -* 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, '"'.freeze)}") + value.gsub!('"'.freeze, """.freeze) + %(#{key}="#{value}") end private @@ -227,10 +228,10 @@ module ActionView # tag("img", src: "open & shut.png") # # => <img src="open & shut.png" /> # - # tag("img", {src: "open & shut.png"}, false, false) + # tag("img", { src: "open & shut.png" }, false, false) # # => <img src="open & 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="["Chicago","IL"]" /> 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 "<p>", 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 & 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 & 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 "<script></script>", 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("<, >, &, ', and "", actual) actual = to_sentence(%w(<script>)) - assert actual.html_safe? + assert_predicate actual, :html_safe? assert_equal("<script>", actual) end @@ -80,19 +80,19 @@ class OutputSafetyHelperTest < ActionView::TestCase url = "https://example.com" expected = %(<a href="#{url}">#{url}</a> and <p><marquee>shady stuff</marquee><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><script>evil_js</script></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><script>evil_js</script></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: &quot; onclick=&quot;alert()&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 <One></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("<", "<").gsub(">", ">") 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"> |