diff options
Diffstat (limited to 'actionview')
67 files changed, 562 insertions, 478 deletions
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 6e71809385..122c42c5bd 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,216 +1,11 @@ -* Remove the option `encode_special_chars` misnomer from `strip_tags` +* Add `:json` type to `auto_discovery_link_tag` to support [JSON Feeds](https://jsonfeed.org/version/1) - As of rails-html-sanitizer v1.0.3, the sanitizer will ignore the - `encode_special_chars` option. + *Mike Gunderloy* - Fixes #28060. +* Update `distance_of_time_in_words` helper to display better error messages + for bad input. - *Andrew Hood* + *Jay Hayes* -## Rails 5.1.0.beta1 (February 23, 2017) ## -* Change the ERB handler from Erubis to Erubi. - - Erubi is an Erubis fork that's svelte, simple, and currently maintained. - Plus it supports `--enable-frozen-string-literal` in Ruby 2.3+. - - Compatibility: Drops support for `<%===` tags for debug output. - These were an unused, undocumented side effect of the Erubis - implementation. - - Deprecation: The Erubis handler will be removed in Rails 5.2, for the - handful of folks using it directly. - - *Jeremy Evans* - -* Allow render locals to be assigned to instance variables in a view. - - Fixes #27480. - - *Andrew White* - -* Add `check_parameters` option to `current_page?` which makes it more strict. - - *Maksym Pugach* - -* Return correct object name in form helper method after `fields_for`. - - Fixes #26931. - - *Yuji Yaginuma* - -* Use `ActionView::Resolver.caching?` (`config.action_view.cache_template_loading`) - to enable template recompilation. - - Before it was enabled by `consider_all_requests_local`, which caused - recompilation in tests. - - *Max Melentiev* - -* Add `form_with` to unify `form_tag` and `form_for` usage. - - Used like `form_tag` (where just the open tag is output): - - ```erb - <%= form_with scope: :post, url: super_special_posts_path %> - ``` - - Used like `form_for`: - - ```erb - <%= form_with model: @post do |form| %> - <%= form.text_field :title %> - <% end %> - ``` - - *Kasper Timm Hansen*, *Marek Kirejczyk* - -* Add `fields` form helper method. - - ```erb - <%= fields :comment, model: @comment do |fields| %> - <%= fields.text_field :title %> - <% end %> - ``` - - Can also be used within form helpers such as `form_with`. - - *Kasper Timm Hansen* - -* Removed deprecated `#original_exception` in `ActionView::Template::Error`. - - *Rafael Mendonça França* - -* Render now accepts any keys for locals, including reserved keywords. - - Only locals with valid variable names get set directly. Others - will still be available in `local_assigns`. - - Example of render with reserved keywords: - - ```erb - <%= render "example", class: "text-center", message: "Hello world!" %> - - <!-- _example.html.erb: --> - <%= tag.div class: local_assigns[:class] do %> - <p><%= message %></p> - <% end %> - ``` - - *Peter Schilling*, *Matthew Draper* - -* Add `:skip_pipeline` option to several asset tag helpers - - `javascript_include_tag`, `stylesheet_link_tag`, `favicon_link_tag`, - `image_tag` and `audio_tag` now accept a `:skip_pipeline` option which can - be set to true to bypass the asset pipeline and serve the assets from the - public folder. - - *Richard Schneeman* - -* Add `:poster_skip_pipeline` option to the `video_tag` helper - - `video_tag` now accepts a `:poster_skip_pipeline` option which can be used - in combination with the `:poster` option to bypass the asset pipeline and - serve the poster image for the video from the public folder. - - *Richard Schneeman* - -* Show cache hits and misses when rendering partials. - - Partials using the `cache` helper will show whether a render hit or missed - the cache: - - ``` - Rendered messages/_message.html.erb in 1.2 ms [cache hit] - Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss] - ``` - - This removes the need for the old fragment cache logging: - - ``` - Read fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/d0bdf2974e1ef6d31685c3b392ad0b74 (0.6ms) - Rendered messages/_message.html.erb in 1.2 ms [cache hit] - Write fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/3b4e249ac9d168c617e32e84b99218b5 (1.1ms) - Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss] - ``` - - Though that full output can be reenabled with - `config.action_controller.enable_fragment_cache_logging = true`. - - *Stan Lo* - -* Changed partial rendering with a collection to allow collections which - implement `to_a`. - - Extracting the collection option had an optimization to avoid unnecessary - queries of ActiveRecord Relations by calling `#to_ary` on the given - collection. Instances of `Enumerator` or `Enumerable` are valid - collections, but they do not implement `#to_ary`. By changing this to - `#to_a`, they will now be extracted and rendered as expected. - - *Steven Harman* - -* New syntax for tag helpers. Avoid positional parameters and support HTML5 by default. - Example usage of tag helpers before: - - ```ruby - tag(:br, nil, true) - content_tag(:div, content_tag(:p, "Hello world!"), class: "strong") - - <%= content_tag :div, class: "strong" do -%> - Hello world! - <% end -%> - ``` - - Example usage of tag helpers after: - - ```ruby - tag.br - tag.div tag.p("Hello world!"), class: "strong" - - <%= tag.div class: "strong" do %> - Hello world! - <% end %> - ``` - - *Marek Kirejczyk*, *Kasper Timm Hansen* - -* Change `datetime_field` and `datetime_field_tag` to generate `datetime-local` fields. - - As a new specification of the HTML 5 the text field type `datetime` will no longer exist - and it is recommended to use `datetime-local`. - Ref: https://html.spec.whatwg.org/multipage/forms.html#local-date-and-time-state-(type=datetime-local) - - *Herminio Torres* - -* Raw template handler (which is also the default template handler in Rails 5) now outputs - HTML-safe strings. - - In Rails 5 the default template handler was changed to the raw template handler. Because - the ERB template handler escaped strings by default this broke some applications that - expected plain JS or HTML files to be rendered unescaped. This fixes the issue caused - by changing the default handler by changing the Raw template handler to output HTML-safe - strings. - - *Eileen M. Uchitelle* - -* `select_tag`'s `include_blank` option for generation for blank option tag, now adds an empty space label, - when the value as well as content for option tag are empty, so that we conform with html specification. - Ref: https://www.w3.org/TR/html5/forms.html#the-option-element. - - Generation of option before: - - ```html - <option value=""></option> - ``` - - Generation of option after: - - ```html - <option value="" label=" "></option> - ``` - - *Vipul A M* - -Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actionview/CHANGELOG.md) for previous changes. +Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionview/CHANGELOG.md) for previous changes. diff --git a/actionview/Rakefile b/actionview/Rakefile index 00ab92129d..0fc38e8db4 100644 --- a/actionview/Rakefile +++ b/actionview/Rakefile @@ -1,10 +1,11 @@ require "rake/testtask" require "fileutils" +require "open3" desc "Default Task" task default: :test -task package: "assets:compile" +task package: %w( assets:compile assets:verify ) # Run the unit tests @@ -84,12 +85,50 @@ namespace :assets do desc "Compile Action View assets" task :compile do require "blade" + require "sprockets" + require "sprockets/export" Blade.build end + + desc "Verify compiled Action View assets" + task :verify do + file = "lib/assets/compiled/rails-ujs.js" + pathname = Pathname.new("#{__dir__}/#{file}") + + print "[verify] #{file} exists " + if pathname.exist? + puts "[OK]" + else + $stderr.puts "[FAIL]" + fail + end + + print "[verify] #{file} is a UMD module " + if pathname.read =~ /module\.exports.*define\.amd/m + puts "[OK]" + else + $stderr.puts "[FAIL]" + fail + end + + print "[verify] #{__dir__} can be required as a module " + js = <<-JS + window = { Event: class {} } + class Element {} + require('#{__dir__}') + JS + _, stderr, status = Open3.capture3("node", "--print", js) + if status.success? + puts "[OK]" + else + $stderr.puts "[FAIL]\n#{stderr}" + fail + end + end end task :lines do - load File.expand_path("..", File.dirname(__FILE__)) + "/tools/line_statistics" + load File.join(File.expand_path("..", __dir__), "/tools/line_statistics") files = FileList["lib/**/*.rb"] CodeTools::LineStatistics.new(files).print_loc end diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec index cfaa5007a1..41221dd04e 100644 --- a/actionview/actionview.gemspec +++ b/actionview/actionview.gemspec @@ -1,4 +1,4 @@ -version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip +version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY diff --git a/actionview/app/assets/javascripts/README.md b/actionview/app/assets/javascripts/README.md index 92f3e8a3b3..0819d5da5f 100644 --- a/actionview/app/assets/javascripts/README.md +++ b/actionview/app/assets/javascripts/README.md @@ -36,10 +36,20 @@ Require `rails-ujs` into your application.js manifest. //= require rails-ujs ``` +Usage with yarn +------------ + +When using with Webpacker gem or your preferred JavaScript bundler. Just add the following to your main JS file and compile. + +```javascript +import Rails from 'rails-ujs'; +Rails.start() +``` + How to run tests ------------ -Run `bundle exec rake ujs:server` first, and then run the web tests by visiting [[http://localhost:4567]] in your browser. +Run `bundle exec rake ujs:server` first, and then run the web tests by visiting http://localhost:4567 in your browser. ## License rails-ujs is released under the [MIT License](MIT-LICENSE). diff --git a/actionview/app/assets/javascripts/config.coffee b/actionview/app/assets/javascripts/config.coffee deleted file mode 100644 index a93325e903..0000000000 --- a/actionview/app/assets/javascripts/config.coffee +++ /dev/null @@ -1,34 +0,0 @@ -#= export Rails - -@Rails = - # Link elements bound by rails-ujs - linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]' - - # Button elements bound by rails-ujs - buttonClickSelector: - selector: 'button[data-remote]:not([form]), button[data-confirm]:not([form])' - exclude: 'form button' - - # Select elements bound by rails-ujs - inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]' - - # Form elements bound by rails-ujs - formSubmitSelector: 'form' - - # Form input elements bound by rails-ujs - formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])' - - # Form input elements disabled during form submission - formDisableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled' - - # Form input elements re-enabled after form submission - formEnableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled' - - # Form file input elements - fileInputSelector: 'input[name][type=file]:not([disabled])' - - # Link onClick disable selector with possible reenable after remote submission - linkDisableSelector: 'a[data-disable-with], a[data-disable]' - - # Button onClick disable selector with possible reenable after remote submission - buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]' diff --git a/actionview/app/assets/javascripts/rails-ujs.coffee b/actionview/app/assets/javascripts/rails-ujs.coffee index df889ce067..bd6e9bb881 100644 --- a/actionview/app/assets/javascripts/rails-ujs.coffee +++ b/actionview/app/assets/javascripts/rails-ujs.coffee @@ -1,75 +1,39 @@ -# -# Unobtrusive JavaScript -# https://github.com/rails/rails-ujs -# -# Released under the MIT license -# -#= require ./config -#= require_tree ./utils -#= require_tree ./features +#= require ./rails-ujs/BANNER +#= export Rails +#= require_self +#= require_tree ./rails-ujs/utils +#= require_tree ./rails-ujs/features +#= require ./rails-ujs/start -{ - fire, delegate - getData, $ - refreshCSRFTokens, CSRFProtection - enableElement, disableElement - handleConfirm - handleRemote, formSubmitButtonClick, handleMetaClick - handleMethod -} = Rails +@Rails = + # Link elements bound by rails-ujs + linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]' -# For backward compatibility -if jQuery? and not jQuery.rails - jQuery.rails = Rails - jQuery.ajaxPrefilter (options, originalOptions, xhr) -> - CSRFProtection(xhr) unless options.crossDomain + # Button elements bound by rails-ujs + buttonClickSelector: + selector: 'button[data-remote]:not([form]), button[data-confirm]:not([form])' + exclude: 'form button' -Rails.start = -> - # Cut down on the number of issues from people inadvertently including - # rails-ujs twice by detecting and raising an error when it happens. - throw new Error('rails-ujs has already been loaded!') if window._rails_loaded + # Select elements bound by rails-ujs + inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]' - # This event works the same as the load event, except that it fires every - # time the page is loaded. - # See https://github.com/rails/jquery-ujs/issues/357 - # See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching - window.addEventListener 'pageshow', -> - $(Rails.formEnableSelector).forEach (el) -> - enableElement(el) if getData(el, 'ujs:disabled') - $(Rails.linkDisableSelector).forEach (el) -> - enableElement(el) if getData(el, 'ujs:disabled') + # Form elements bound by rails-ujs + formSubmitSelector: 'form' - delegate document, Rails.linkDisableSelector, 'ajax:complete', enableElement - delegate document, Rails.linkDisableSelector, 'ajax:stopped', enableElement - delegate document, Rails.buttonDisableSelector, 'ajax:complete', enableElement - delegate document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement + # Form input elements bound by rails-ujs + formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])' - delegate document, Rails.linkClickSelector, 'click', handleConfirm - delegate document, Rails.linkClickSelector, 'click', handleMetaClick - delegate document, Rails.linkClickSelector, 'click', disableElement - delegate document, Rails.linkClickSelector, 'click', handleRemote - delegate document, Rails.linkClickSelector, 'click', handleMethod + # Form input elements disabled during form submission + formDisableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled' - delegate document, Rails.buttonClickSelector, 'click', handleConfirm - delegate document, Rails.buttonClickSelector, 'click', disableElement - delegate document, Rails.buttonClickSelector, 'click', handleRemote + # Form input elements re-enabled after form submission + formEnableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled' - delegate document, Rails.inputChangeSelector, 'change', handleConfirm - delegate document, Rails.inputChangeSelector, 'change', handleRemote + # Form file input elements + fileInputSelector: 'input[name][type=file]:not([disabled])' - delegate document, Rails.formSubmitSelector, 'submit', handleConfirm - delegate document, Rails.formSubmitSelector, 'submit', handleRemote - # Normal mode submit - # Slight timeout so that the submit button gets properly serialized - delegate document, Rails.formSubmitSelector, 'submit', (e) -> setTimeout((-> disableElement(e)), 13) - delegate document, Rails.formSubmitSelector, 'ajax:send', disableElement - delegate document, Rails.formSubmitSelector, 'ajax:complete', enableElement + # Link onClick disable selector with possible reenable after remote submission + linkDisableSelector: 'a[data-disable-with], a[data-disable]' - delegate document, Rails.formInputClickSelector, 'click', handleConfirm - delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick - - document.addEventListener('DOMContentLoaded', refreshCSRFTokens) - window._rails_loaded = true - -if window.Rails is Rails and fire(document, 'rails:attachBindings') - Rails.start() + # Button onClick disable selector with possible reenable after remote submission + buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]' diff --git a/actionview/app/assets/javascripts/rails-ujs/BANNER.js b/actionview/app/assets/javascripts/rails-ujs/BANNER.js new file mode 100644 index 0000000000..47ecd66003 --- /dev/null +++ b/actionview/app/assets/javascripts/rails-ujs/BANNER.js @@ -0,0 +1,5 @@ +/* +Unobtrusive JavaScript +https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts +Released under the MIT license + */ diff --git a/actionview/app/assets/javascripts/features/confirm.coffee b/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee index 72b5aaa218..72b5aaa218 100644 --- a/actionview/app/assets/javascripts/features/confirm.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee diff --git a/actionview/app/assets/javascripts/features/disable.coffee b/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee index e8cce7da40..90aa3bdf0e 100644 --- a/actionview/app/assets/javascripts/features/disable.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee @@ -2,6 +2,10 @@ { matches, getData, setData, stopEverything, formElements } = Rails +Rails.handleDisabledElement = (e) -> + element = this + stopEverything(e) if element.disabled + # Unified function to enable an element (link, button and form) Rails.enableElement = (e) -> element = if e instanceof Event then e.target else e diff --git a/actionview/app/assets/javascripts/features/method.coffee b/actionview/app/assets/javascripts/rails-ujs/features/method.coffee index d04d9414dd..d04d9414dd 100644 --- a/actionview/app/assets/javascripts/features/method.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/features/method.coffee diff --git a/actionview/app/assets/javascripts/features/remote.coffee b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee index 852587042c..852587042c 100644 --- a/actionview/app/assets/javascripts/features/remote.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee diff --git a/actionview/app/assets/javascripts/rails-ujs/start.coffee b/actionview/app/assets/javascripts/rails-ujs/start.coffee new file mode 100644 index 0000000000..5746a22287 --- /dev/null +++ b/actionview/app/assets/javascripts/rails-ujs/start.coffee @@ -0,0 +1,70 @@ +{ + fire, delegate + getData, $ + refreshCSRFTokens, CSRFProtection + enableElement, disableElement, handleDisabledElement + handleConfirm + handleRemote, formSubmitButtonClick, handleMetaClick + handleMethod +} = Rails + +# For backward compatibility +if jQuery? and not jQuery.rails + jQuery.rails = Rails + jQuery.ajaxPrefilter (options, originalOptions, xhr) -> + CSRFProtection(xhr) unless options.crossDomain + +Rails.start = -> + # Cut down on the number of issues from people inadvertently including + # rails-ujs twice by detecting and raising an error when it happens. + throw new Error('rails-ujs has already been loaded!') if window._rails_loaded + + # This event works the same as the load event, except that it fires every + # time the page is loaded. + # See https://github.com/rails/jquery-ujs/issues/357 + # See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching + window.addEventListener 'pageshow', -> + $(Rails.formEnableSelector).forEach (el) -> + enableElement(el) if getData(el, 'ujs:disabled') + $(Rails.linkDisableSelector).forEach (el) -> + enableElement(el) if getData(el, 'ujs:disabled') + + delegate document, Rails.linkDisableSelector, 'ajax:complete', enableElement + delegate document, Rails.linkDisableSelector, 'ajax:stopped', enableElement + delegate document, Rails.buttonDisableSelector, 'ajax:complete', enableElement + delegate document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement + + delegate document, Rails.linkClickSelector, 'click', handleDisabledElement + delegate document, Rails.linkClickSelector, 'click', handleConfirm + delegate document, Rails.linkClickSelector, 'click', handleMetaClick + delegate document, Rails.linkClickSelector, 'click', disableElement + delegate document, Rails.linkClickSelector, 'click', handleRemote + delegate document, Rails.linkClickSelector, 'click', handleMethod + + delegate document, Rails.buttonClickSelector, 'click', handleDisabledElement + delegate document, Rails.buttonClickSelector, 'click', handleConfirm + delegate document, Rails.buttonClickSelector, 'click', disableElement + delegate document, Rails.buttonClickSelector, 'click', handleRemote + + delegate document, Rails.inputChangeSelector, 'change', handleDisabledElement + delegate document, Rails.inputChangeSelector, 'change', handleConfirm + delegate document, Rails.inputChangeSelector, 'change', handleRemote + + delegate document, Rails.formSubmitSelector, 'submit', handleDisabledElement + delegate document, Rails.formSubmitSelector, 'submit', handleConfirm + delegate document, Rails.formSubmitSelector, 'submit', handleRemote + # Normal mode submit + # Slight timeout so that the submit button gets properly serialized + delegate document, Rails.formSubmitSelector, 'submit', (e) -> setTimeout((-> disableElement(e)), 13) + delegate document, Rails.formSubmitSelector, 'ajax:send', disableElement + delegate document, Rails.formSubmitSelector, 'ajax:complete', enableElement + + delegate document, Rails.formInputClickSelector, 'click', handleDisabledElement + delegate document, Rails.formInputClickSelector, 'click', handleConfirm + delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick + + document.addEventListener('DOMContentLoaded', refreshCSRFTokens) + window._rails_loaded = true + +if window.Rails is Rails and fire(document, 'rails:attachBindings') + Rails.start() diff --git a/actionview/app/assets/javascripts/utils/ajax.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee index 9af515beda..a653d3af3d 100644 --- a/actionview/app/assets/javascripts/utils/ajax.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee @@ -14,7 +14,7 @@ AcceptHeaders = Rails.ajax = (options) -> options = prepareOptions(options) xhr = createXHR options, -> - response = processResponse(xhr.response, xhr.getResponseHeader('Content-Type')) + response = processResponse(xhr.response ? xhr.responseText, xhr.getResponseHeader('Content-Type')) if xhr.status // 100 == 2 options.success?(response, xhr.statusText, xhr) else @@ -29,6 +29,7 @@ Rails.ajax = (options) -> fire(document, 'ajaxStop') # to be compatible with jQuery.ajax prepareOptions = (options) -> + options.url = options.url or location.href options.type = options.type.toUpperCase() # append data to url if it's a GET request if options.type is 'GET' and options.data @@ -63,10 +64,10 @@ processResponse = (response, type) -> if typeof response is 'string' and typeof type is 'string' if type.match(/\bjson\b/) try response = JSON.parse(response) - else if type.match(/\bjavascript\b/) + else if type.match(/\b(?:java|ecma)script\b/) script = document.createElement('script') - script.innerHTML = response - document.body.appendChild(script) + script.text = response + document.head.appendChild(script).parentNode.removeChild(script) else if type.match(/\b(xml|html|svg)\b/) parser = new DOMParser() type = type.replace(/;.+/, '') # remove something like ';charset=utf-8' diff --git a/actionview/app/assets/javascripts/utils/csrf.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/csrf.coffee index 4eb5ebb414..4eb5ebb414 100644 --- a/actionview/app/assets/javascripts/utils/csrf.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/utils/csrf.coffee diff --git a/actionview/app/assets/javascripts/utils/dom.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee index 6bef618147..6bef618147 100644 --- a/actionview/app/assets/javascripts/utils/dom.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee diff --git a/actionview/app/assets/javascripts/utils/event.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee index 8d3ff007ea..8d3ff007ea 100644 --- a/actionview/app/assets/javascripts/utils/event.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee diff --git a/actionview/app/assets/javascripts/utils/form.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/form.coffee index 5fa337b518..5fa337b518 100644 --- a/actionview/app/assets/javascripts/utils/form.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/utils/form.coffee diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb index ca586f1d52..99c5b831b5 100644 --- a/actionview/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -92,5 +92,5 @@ end require "active_support/core_ext/string/output_safety" ActiveSupport.on_load(:i18n) do - I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml" + I18n.load_path << File.expand_path("action_view/locale/en.yml", __dir__) end diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 0658d8601d..5ddf1ceb66 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -62,8 +62,10 @@ module ActionView node end else - logger.error " '#{name}' file doesn't exist, so no dependencies" - logger.error " Couldn't find template for digesting: #{name}" + unless name.include?("#") # Dynamic template partial names can never be tracked + logger.error " Couldn't find template for digesting: #{name}" + end + seen[name] ||= Missing.new(name, logical_name, nil) end end diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb index 662a85f191..92e21d7a4f 100644 --- a/actionview/lib/action_view/gem_version.rb +++ b/actionview/lib/action_view/gem_version.rb @@ -6,9 +6,9 @@ module ActionView module VERSION MAJOR = 5 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "beta1" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index 750f96f29e..c21fe782c6 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -122,9 +122,9 @@ module ActionView end # Returns a link tag that browsers and feed readers can use to auto-detect - # an RSS or Atom feed. The +type+ can either be <tt>:rss</tt> (default) or - # <tt>:atom</tt>. Control the link options in url_for format using the - # +url_options+. You can modify the LINK tag itself in +tag_options+. + # an RSS, Atom, or JSON feed. The +type+ can be <tt>:rss</tt> (default), + # <tt>:atom</tt>, or <tt>:json</tt>. Control the link options in url_for format + # using the +url_options+. You can modify the LINK tag itself in +tag_options+. # # ==== Options # @@ -138,6 +138,8 @@ module ActionView # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" /> # auto_discovery_link_tag(:atom) # # => <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" /> + # auto_discovery_link_tag(:json) + # # => <link rel="alternate" type="application/json" title="JSON" href="http://www.currenthost.com/controller/action" /> # auto_discovery_link_tag(:rss, {action: "feed"}) # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" /> # auto_discovery_link_tag(:rss, {action: "feed"}, {title: "My RSS"}) @@ -147,8 +149,8 @@ module ActionView # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "Example RSS"}) # # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed.rss" /> def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {}) - if !(type == :rss || type == :atom) && tag_options[:type].blank? - raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss or :atom.") + if !(type == :rss || type == :atom || type == :json) && tag_options[:type].blank? + raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss, :atom, or :json.") end tag( diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index 15ab7e304f..c3aecadcd6 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -8,10 +8,9 @@ module ActionView # fragments, and so on. This method takes a block that contains # the content you wish to cache. # - # The best way to use this is by doing key-based cache expiration - # on top of a cache store like Memcached that'll automatically - # kick out old entries. For more on key-based expiration, see: - # http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works + # The best way to use this is by doing recyclable key-based cache expiration + # on top of a cache store like Memcached or Redis that'll automatically + # kick out old entries. # # When using this method, you list the cache dependency as the name of the cache, like so: # @@ -23,10 +22,14 @@ module ActionView # This approach will assume that when a new topic is added, you'll touch # the project. The cache key generated from this call will be something like: # - # views/projects/123-20120806214154/7a1156131a6928cb0026877f8b749ac9 - # ^class ^id ^updated_at ^template tree digest + # views/template/action.html.erb:7a1156131a6928cb0026877f8b749ac9/projects/123 + # ^template path ^template tree digest ^class ^id # - # The cache is thus automatically bumped whenever the project updated_at is touched. + # This cache key is stable, but it's combined with a cache version derived from the project + # record. When the project updated_at is touched, the #cache_version changes, even + # if the key stays stable. This means that unlike a traditional key-based cache expiration + # approach, you won't be generating cache trash, unused keys, simply because the dependent + # record is updated. # # If your template cache depends on multiple sources (try to avoid this to keep things simple), # you can name all these dependencies as part of an array: @@ -217,10 +220,15 @@ module ActionView def fragment_name_with_digest(name, virtual_path) virtual_path ||= @virtual_path + if virtual_path name = controller.url_for(name).split("://").last if name.is_a?(Hash) - digest = Digestor.digest name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies - [ name, digest ] + + if digest = Digestor.digest(name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies).presence + [ "#{virtual_path}:#{digest}", name ] + else + [ virtual_path, name ] + end else name end diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index 09dc6ef6bd..3f43465aa4 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -95,8 +95,8 @@ module ActionView scope: :'datetime.distance_in_words' }.merge!(options) - from_time = from_time.to_time if from_time.respond_to?(:to_time) - to_time = to_time.to_time if to_time.respond_to?(:to_time) + from_time = normalize_distance_of_time_argument_to_time(from_time) + to_time = normalize_distance_of_time_argument_to_time(to_time) from_time, to_time = to_time, from_time if from_time > to_time distance_in_minutes = ((to_time - from_time) / 60.0).round distance_in_seconds = (to_time - from_time).round @@ -130,22 +130,18 @@ module ActionView # 60 days up to 365 days when 86400...525600 then locale.t :x_months, count: (distance_in_minutes.to_f / 43200.0).round else - if from_time.acts_like?(:time) && to_time.acts_like?(:time) - fyear = from_time.year - fyear += 1 if from_time.month >= 3 - tyear = to_time.year - tyear -= 1 if to_time.month < 3 - leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count { |x| Date.leap?(x) } - minute_offset_for_leap_year = leap_years * 1440 - # Discount the leap year days when calculating year distance. - # e.g. if there are 20 leap year days between 2 dates having the same day - # and month then the based on 365 days calculation - # the distance in years will come out to over 80 years when in written - # English it would read better as about 80 years. - minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year - else - minutes_with_offset = distance_in_minutes - end + from_year = from_time.year + from_year += 1 if from_time.month >= 3 + to_year = to_time.year + to_year -= 1 if to_time.month < 3 + leap_years = (from_year > to_year) ? 0 : (from_year..to_year).count { |x| Date.leap?(x) } + minute_offset_for_leap_year = leap_years * 1440 + # Discount the leap year days when calculating year distance. + # e.g. if there are 20 leap year days between 2 dates having the same day + # and month then the based on 365 days calculation + # the distance in years will come out to over 80 years when in written + # English it would read better as about 80 years. + minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year remainder = (minutes_with_offset % MINUTES_IN_YEAR) distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR) if remainder < MINUTES_IN_QUARTER_YEAR @@ -687,6 +683,18 @@ module ActionView content_tag("time".freeze, content, options.reverse_merge(datetime: datetime), &block) end + + private + + def normalize_distance_of_time_argument_to_time(value) + if value.is_a?(Numeric) + Time.at(value) + elsif value.respond_to?(:to_time) + value.to_time + else + raise ArgumentError, "#{value.inspect} can't be converted to a Time value" + end + end end class DateTimeSelector #:nodoc: diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index 26a625e4fe..3eafe0028e 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -201,9 +201,9 @@ module ActionView # <%= f.submit %> # <% end %> # - # This also works for the methods in FormOptionHelper and DateHelper that + # This also works for the methods in FormOptionsHelper and DateHelper that # are designed to work with an object as base, like - # FormOptionHelper#collection_select and DateHelper#datetime_select. + # FormOptionsHelper#collection_select and DateHelper#datetime_select. # # === #form_for with a model object # @@ -416,13 +416,13 @@ module ActionView # # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter # - # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| + # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %> # ... # <% end %> # # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>: # - # <%= form_for @invoice, url: external_url, authenticity_token: false do |f| + # <%= form_for @invoice, url: external_url, authenticity_token: false do |f| %> # ... # <% end %> def form_for(record, options = {}, &block) @@ -474,6 +474,8 @@ module ActionView end private :apply_form_for_options! + mattr_accessor(:form_with_generates_remote_forms) { true } + # Creates a form tag based on mixing URLs, scopes, or models. # # # Using just a URL: @@ -632,9 +634,9 @@ module ActionView # <%= form.submit %> # <% end %> # - # Same goes for the methods in FormOptionHelper and DateHelper designed + # Same goes for the methods in FormOptionsHelper and DateHelper designed # to work with an object as a base, like - # FormOptionHelper#collection_select and DateHelper#datetime_select. + # FormOptionsHelper#collection_select and DateHelper#datetime_select. # # === Setting the method # @@ -791,9 +793,9 @@ module ActionView # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is # of class +Permission+, the field will still be named <tt>permission[admin]</tt>. # - # Note: This also works for the methods in FormOptionHelper and + # Note: This also works for the methods in FormOptionsHelper and # DateHelper that are designed to work with an object as base, like - # FormOptionHelper#collection_select and DateHelper#datetime_select. + # FormOptionsHelper#collection_select and DateHelper#datetime_select. # # === Nested Attributes Examples # @@ -1033,9 +1035,9 @@ module ActionView # <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %> # <% end %> # - # Same goes for the methods in FormOptionHelper and DateHelper designed + # Same goes for the methods in FormOptionsHelper and DateHelper designed # to work with an object as a base, like - # FormOptionHelper#collection_select and DateHelper#datetime_select. + # FormOptionsHelper#collection_select and DateHelper#datetime_select. def fields(scope = nil, model: nil, **options, &block) options[:allow_method_names_outside_object] = true options[:skip_default_ids] = true @@ -1503,7 +1505,7 @@ module ActionView end private - def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: false, + def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms, skip_enforcing_utf8: false, **options) html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html) html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted? @@ -1517,12 +1519,14 @@ module ActionView html_options[:"accept-charset"] = "UTF-8" html_options[:"data-remote"] = true unless local - if !local && !embed_authenticity_token_in_remote_forms && - html_options[:authenticity_token].blank? - # The authenticity token is taken from the meta tag in this case - html_options[:authenticity_token] = false - elsif html_options[:authenticity_token] == true - # Include the default authenticity_token, which is only generated when its set to nil, + html_options[:authenticity_token] = options.delete(:authenticity_token) + + if !local && html_options[:authenticity_token].blank? + html_options[:authenticity_token] = embed_authenticity_token_in_remote_forms + end + + if html_options[:authenticity_token] == true + # Include the default authenticity_token, which is only generated when it's set to nil, # but we needed the true value to override the default of no authenticity_token on data-remote. html_options[:authenticity_token] = nil end @@ -1724,9 +1728,9 @@ module ActionView # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is # of class +Permission+, the field will still be named <tt>permission[admin]</tt>. # - # Note: This also works for the methods in FormOptionHelper and + # Note: This also works for the methods in FormOptionsHelper and # DateHelper that are designed to work with an object as base, like - # FormOptionHelper#collection_select and DateHelper#datetime_select. + # FormOptionsHelper#collection_select and DateHelper#datetime_select. # # === Nested Attributes Examples # diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index ffc64e7118..9fc08b3837 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -18,7 +18,7 @@ module ActionView include TextHelper mattr_accessor :embed_authenticity_token_in_remote_forms - self.embed_authenticity_token_in_remote_forms = false + self.embed_authenticity_token_in_remote_forms = nil # 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. diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index 0895533a60..aa420c4b66 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -149,7 +149,7 @@ module ActionView end value = options.fetch(:selected) { value(object) } - select = content_tag("select", add_options(option_tags, options, value), html_options) + select = content_tag("select", add_options(option_tags, options, value), html_options.except!("skip_default_ids", "allow_method_names_outside_object")) if html_options["multiple"] && options.fetch(:include_hidden, true) tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "") + select diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb index 667c7e945a..9ff7e54e4f 100644 --- a/actionview/lib/action_view/helpers/tags/select.rb +++ b/actionview/lib/action_view/helpers/tags/select.rb @@ -33,7 +33,7 @@ module ActionView # [nil, []] # { nil => [] } def grouped_choices? - !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last + !@choices.blank? && @choices.first.respond_to?(:last) && Array === @choices.first.last end end end diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index a306903c60..a6857101b9 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -542,7 +542,7 @@ module ActionView return false unless request.get? || request.head? - check_parameters ||= !options.is_a?(String) && options.try(:delete, :check_parameters) + check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters) url_string = URI.parser.unescape(url_for(options)).force_encoding(Encoding::BINARY) # We ignore any extra parameters in the request_uri if the @@ -621,11 +621,6 @@ module ActionView # # => [{name: 'country[name]', value: 'Denmark'}] def to_form_params(attribute, namespace = nil) attribute = if attribute.respond_to?(:permitted?) - unless attribute.permitted? - raise ArgumentError, "Attempting to generate a buttom from non-sanitized request parameters!" \ - " Whitelist and sanitize passed parameters to be secure." - end - attribute.to_h else attribute diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb index d344d98f4b..61678933e9 100644 --- a/actionview/lib/action_view/railtie.rb +++ b/actionview/lib/action_view/railtie.rb @@ -5,7 +5,7 @@ module ActionView # = Action View Railtie class Railtie < Rails::Engine # :nodoc: config.action_view = ActiveSupport::OrderedOptions.new - config.action_view.embed_authenticity_token_in_remote_forms = false + config.action_view.embed_authenticity_token_in_remote_forms = nil config.action_view.debug_missing_translation = true config.eager_load_namespaces << ActionView @@ -17,6 +17,15 @@ module ActionView end end + initializer "action_view.form_with_generates_remote_forms" do |app| + ActiveSupport.on_load(:action_view) do + form_with_generates_remote_forms = app.config.action_view.delete(:form_with_generates_remote_forms) + unless form_with_generates_remote_forms.nil? + ActionView::Helpers::FormHelper.form_with_generates_remote_forms = form_with_generates_remote_forms + end + 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/collection_caching.rb b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb index 1fbe209200..847256ac78 100644 --- a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb +++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb @@ -38,7 +38,7 @@ module ActionView end def expanded_cache_key(key) - key = @view.fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path)) + key = @view.combined_fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path)) key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0. end diff --git a/actionview/package.json b/actionview/package.json index a1da13315e..85f4ddacbe 100644 --- a/actionview/package.json +++ b/actionview/package.json @@ -1,6 +1,6 @@ { "name": "rails-ujs", - "version": "5.1.0-beta1", + "version": "5.2.0-alpha", "description": "Ruby on Rails unobtrusive scripting adapter", "main": "lib/assets/compiled/rails-ujs.js", "files": [ @@ -11,7 +11,7 @@ }, "scripts": { "build": "bundle exec blade build", - "test": "echo \"See the README: https://github.com/rails/rails-ujs#how-to-run-tests\" && exit 1", + "test": "echo \"See the README: https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts#how-to-run-tests\" && exit 1", "lint": "coffeelint app/assets/javascripts && eslint test/public/test" }, "repository": { diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb index dde66a7ba0..a7d706c5e1 100644 --- a/actionview/test/abstract_unit.rb +++ b/actionview/test/abstract_unit.rb @@ -1,8 +1,8 @@ -$:.unshift(File.dirname(__FILE__) + "/lib") -$:.unshift(File.dirname(__FILE__) + "/fixtures/helpers") -$:.unshift(File.dirname(__FILE__) + "/fixtures/alternate_helpers") +$:.unshift File.expand_path("lib", __dir__) +$:.unshift File.expand_path("fixtures/helpers", __dir__) +$:.unshift File.expand_path("fixtures/alternate_helpers", __dir__) -ENV["TMPDIR"] = File.join(File.dirname(__FILE__), "tmp") +ENV["TMPDIR"] = File.expand_path("tmp", __dir__) require "active_support/core_ext/kernel/reporting" @@ -47,7 +47,7 @@ I18n.backend.store_translations "da", {} I18n.backend.store_translations "pt-BR", {} ORIGINAL_LOCALES = I18n.available_locales.map(&:to_s).sort -FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), "fixtures") +FIXTURE_LOAD_PATH = File.expand_path("fixtures", __dir__) module RenderERBUtils def view @@ -133,7 +133,7 @@ class BasicController def config @config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config).tap do |config| # VIEW TODO: View tests should not require a controller - public_dir = File.expand_path("../fixtures/public", __FILE__) + public_dir = File.expand_path("fixtures/public", __dir__) config.assets_dir = public_dir config.javascripts_dir = "#{public_dir}/javascripts" config.stylesheets_dir = "#{public_dir}/stylesheets" @@ -196,7 +196,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase end def with_autoload_path(path) - path = File.join(File.dirname(__FILE__), "fixtures", path) + path = File.join(File.expand_path("fixtures", __dir__), path) if ActiveSupport::Dependencies.autoload_paths.include?(path) yield else diff --git a/actionview/test/actionpack/abstract/abstract_controller_test.rb b/actionview/test/actionpack/abstract/abstract_controller_test.rb index a2cd3deb58..8f65a61493 100644 --- a/actionview/test/actionpack/abstract/abstract_controller_test.rb +++ b/actionview/test/actionpack/abstract/abstract_controller_test.rb @@ -42,7 +42,7 @@ module AbstractController super end - append_view_path File.expand_path(File.join(File.dirname(__FILE__), "views")) + append_view_path File.expand_path("views", __dir__) end class Me2 < RenderingController @@ -152,7 +152,7 @@ module AbstractController class OverridingLocalPrefixes < AbstractController::Base include AbstractController::Rendering include ActionView::Rendering - append_view_path File.expand_path(File.join(File.dirname(__FILE__), "views")) + append_view_path File.expand_path("views", __dir__) def index render diff --git a/actionview/test/actionpack/abstract/helper_test.rb b/actionview/test/actionpack/abstract/helper_test.rb index 83237518d7..13922e4485 100644 --- a/actionview/test/actionpack/abstract/helper_test.rb +++ b/actionview/test/actionpack/abstract/helper_test.rb @@ -1,6 +1,6 @@ require "abstract_unit" -ActionController::Base.helpers_path = File.expand_path("../../../fixtures/helpers", __FILE__) +ActionController::Base.helpers_path = File.expand_path("../../fixtures/helpers", __dir__) module AbstractController module Testing @@ -51,7 +51,7 @@ module AbstractController class AbstractInvalidHelpers < AbstractHelpers include ActionController::Helpers - path = File.expand_path("../../../fixtures/helpers_missing", __FILE__) + path = File.expand_path("../../fixtures/helpers_missing", __dir__) $:.unshift(path) self.helpers_path = path end diff --git a/actionview/test/actionpack/controller/capture_test.rb b/actionview/test/actionpack/controller/capture_test.rb index f0ae609845..cc3a23c60c 100644 --- a/actionview/test/actionpack/controller/capture_test.rb +++ b/actionview/test/actionpack/controller/capture_test.rb @@ -2,7 +2,7 @@ require "abstract_unit" require "active_support/logger" class CaptureController < ActionController::Base - self.view_paths = [ File.dirname(__FILE__) + "/../../fixtures/actionpack" ] + self.view_paths = [ File.expand_path("../../fixtures/actionpack", __dir__) ] def self.controller_name; "test"; end def self.controller_path; "test"; end diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb index b79835ff34..b3e0329f57 100644 --- a/actionview/test/actionpack/controller/layout_test.rb +++ b/actionview/test/actionpack/controller/layout_test.rb @@ -5,7 +5,7 @@ require "active_support/core_ext/array/extract_options" # method has access to the view_paths array when looking for a layout to automatically assign. old_load_paths = ActionController::Base.view_paths -ActionController::Base.view_paths = [ File.dirname(__FILE__) + "/../../fixtures/actionpack/layout_tests/" ] +ActionController::Base.view_paths = [ File.expand_path("../../fixtures/actionpack/layout_tests", __dir__) ] class LayoutTest < ActionController::Base def self.controller_path; "views" end @@ -96,7 +96,7 @@ class StreamingLayoutController < LayoutTest end class AbsolutePathLayoutController < LayoutTest - layout File.expand_path(File.expand_path(__FILE__) + "/../../../fixtures/actionpack/layout_tests/layouts/layout_test") + layout File.expand_path("../../fixtures/actionpack/layout_tests/layouts/layout_test", __dir__) end class HasOwnLayoutController < LayoutTest @@ -117,7 +117,7 @@ end class PrependsViewPathController < LayoutTest def hello - prepend_view_path File.dirname(__FILE__) + "/../../fixtures/actionpack/layout_tests/alt/" + prepend_view_path File.expand_path("../../fixtures/actionpack/layout_tests/alt", __dir__) render layout: "alt" end end diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb index 51ec8899b1..6528169312 100644 --- a/actionview/test/actionpack/controller/render_test.rb +++ b/actionview/test/actionpack/controller/render_test.rb @@ -56,7 +56,7 @@ class TestController < ApplicationController end def hello_world_file - render file: File.expand_path("../../../fixtures/actionpack/hello", __FILE__), formats: [:html] + render file: File.expand_path("../../fixtures/actionpack/hello", __dir__), formats: [:html] end # :ported: @@ -125,7 +125,7 @@ class TestController < ApplicationController # :ported: def render_file_with_instance_variables @secret = "in the sauce" - path = File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_ivar") + path = File.expand_path("../../fixtures/test/render_file_with_ivar", __dir__) render file: path end @@ -142,21 +142,21 @@ class TestController < ApplicationController def render_file_using_pathname @secret = "in the sauce" - render file: Pathname.new(File.dirname(__FILE__)).join("..", "..", "fixtures", "test", "dot.directory", "render_file_with_ivar") + render file: Pathname.new(__dir__).join("..", "..", "fixtures", "test", "dot.directory", "render_file_with_ivar") end def render_file_from_template @secret = "in the sauce" - @path = File.expand_path(File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_ivar")) + @path = File.expand_path("../../fixtures/test/render_file_with_ivar", __dir__) end def render_file_with_locals - path = File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_locals") + path = File.expand_path("../../fixtures/test/render_file_with_locals", __dir__) render file: path, locals: { secret: "in the sauce" } end def render_file_as_string_with_locals - path = File.expand_path(File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_locals")) + path = File.expand_path("../../fixtures/test/render_file_with_locals", __dir__) render file: path, locals: { secret: "in the sauce" } end diff --git a/actionview/test/active_record_unit.rb b/actionview/test/active_record_unit.rb index 7f94b7ebb4..901c0e2b3e 100644 --- a/actionview/test/active_record_unit.rb +++ b/actionview/test/active_record_unit.rb @@ -13,7 +13,7 @@ end # Try to grab AR unless defined?(ActiveRecord) && defined?(FixtureSet) begin - PATH_TO_AR = "#{File.dirname(__FILE__)}/../../activerecord/lib" + PATH_TO_AR = File.expand_path("../../activerecord/lib", __dir__) raise LoadError, "#{PATH_TO_AR} doesn't exist" unless File.directory?(PATH_TO_AR) $LOAD_PATH.unshift PATH_TO_AR require "active_record" @@ -58,13 +58,13 @@ class ActiveRecordTestConnector # Load actionpack sqlite3 tables def load_schema - File.read(File.dirname(__FILE__) + "/fixtures/db_definitions/sqlite.sql").split(";").each do |sql| + File.read(File.expand_path("fixtures/db_definitions/sqlite.sql", __dir__)).split(";").each do |sql| ActiveRecord::Base.connection.execute(sql) unless sql.blank? end end def require_fixture_models - Dir.glob(File.dirname(__FILE__) + "/fixtures/*.rb").each { |f| require f } + Dir.glob(File.expand_path("fixtures/*.rb", __dir__)).each { |f| require f } end end end diff --git a/actionview/test/activerecord/controller_runtime_test.rb b/actionview/test/activerecord/controller_runtime_test.rb index 590559f592..1cec5072c0 100644 --- a/actionview/test/activerecord/controller_runtime_test.rb +++ b/actionview/test/activerecord/controller_runtime_test.rb @@ -67,7 +67,7 @@ class ControllerRuntimeLogSubscriberTest < ActionController::TestCase wait assert_equal 2, @logger.logged(:info).size - assert_match(/\(Views: [\d.]+ms \| ActiveRecord: 0.0ms\)/, @logger.logged(:info)[1]) + assert_match(/\(Views: [\d.]+ms \| ActiveRecord: 0\.0ms\)/, @logger.logged(:info)[1]) end def test_log_with_active_record_when_post diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb index e99c769dc2..b2e0fb08c4 100644 --- a/actionview/test/activerecord/polymorphic_routes_test.rb +++ b/actionview/test/activerecord/polymorphic_routes_test.rb @@ -730,3 +730,49 @@ class PolymorphicPathRoutesTest < PolymorphicRoutesTest assert_equal url.sub(/http:\/\/#{host}/, ""), url_for(args) end end + +class DirectRoutesTest < ActionView::TestCase + class Linkable + attr_reader :id + + def self.name + super.demodulize + end + + def initialize(id) + @id = id + end + + def linkable_type + self.class.name.underscore + end + end + + class Category < Linkable; end + class Collection < Linkable; end + class Product < Linkable; end + + Routes = ActionDispatch::Routing::RouteSet.new + Routes.draw do + resources :categories, :collections, :products + direct(:linkable) { |linkable| [:"#{linkable.linkable_type}", { id: linkable.id }] } + end + + include Routes.url_helpers + + def setup + @category = Category.new("1") + @collection = Collection.new("2") + @product = Product.new("3") + end + + def test_direct_routes + assert_equal "/categories/1", linkable_path(@category) + assert_equal "/collections/2", linkable_path(@collection) + assert_equal "/products/3", linkable_path(@product) + + assert_equal "http://test.host/categories/1", linkable_url(@category) + assert_equal "http://test.host/collections/2", linkable_url(@collection) + assert_equal "http://test.host/products/3", linkable_url(@product) + end +end diff --git a/actionview/test/activerecord/relation_cache_test.rb b/actionview/test/activerecord/relation_cache_test.rb index 43f7242ee9..fbab512c41 100644 --- a/actionview/test/activerecord/relation_cache_test.rb +++ b/actionview/test/activerecord/relation_cache_test.rb @@ -10,7 +10,7 @@ class RelationCacheTest < ActionView::TestCase def test_cache_relation_other cache(Project.all) { concat("Hello World") } - assert_equal "Hello World", controller.cache_store.read("views/projects-#{Project.count}/") + assert_equal "Hello World", controller.cache_store.read("views/path/projects-#{Project.count}") end def view_cache_dependencies; end diff --git a/actionview/test/fixtures/actionpack/layouts/builder.builder b/actionview/test/fixtures/actionpack/layouts/builder.builder index 7c7d4b2dd1..c55488edd0 100644 --- a/actionview/test/fixtures/actionpack/layouts/builder.builder +++ b/actionview/test/fixtures/actionpack/layouts/builder.builder @@ -1,3 +1,3 @@ xml.wrapper do xml << yield -end
\ No newline at end of file +end diff --git a/actionview/test/fixtures/actionpack/test/_hello.builder b/actionview/test/fixtures/actionpack/test/_hello.builder index ef52f632d1..fc72df16d0 100644 --- a/actionview/test/fixtures/actionpack/test/_hello.builder +++ b/actionview/test/fixtures/actionpack/test/_hello.builder @@ -1 +1 @@ -xm.hello
\ No newline at end of file +xm.hello diff --git a/actionview/test/fixtures/actionpack/test/formatted_xml_erb.builder b/actionview/test/fixtures/actionpack/test/formatted_xml_erb.builder index 14fd3549fb..f98aaa34a5 100644 --- a/actionview/test/fixtures/actionpack/test/formatted_xml_erb.builder +++ b/actionview/test/fixtures/actionpack/test/formatted_xml_erb.builder @@ -1 +1 @@ -xml.test 'failed'
\ No newline at end of file +xml.test "failed" diff --git a/actionview/test/fixtures/actionpack/test/hello.builder b/actionview/test/fixtures/actionpack/test/hello.builder index a471553941..b8ab17ad5b 100644 --- a/actionview/test/fixtures/actionpack/test/hello.builder +++ b/actionview/test/fixtures/actionpack/test/hello.builder @@ -1,4 +1,4 @@ xml.html do xml.p "Hello #{@name}" - xml << render(:file => "test/greeting") -end
\ No newline at end of file + xml << render(file: "test/greeting") +end diff --git a/actionview/test/fixtures/actionpack/test/hello_world_container.builder b/actionview/test/fixtures/actionpack/test/hello_world_container.builder index e48d75c405..24032ab5e0 100644 --- a/actionview/test/fixtures/actionpack/test/hello_world_container.builder +++ b/actionview/test/fixtures/actionpack/test/hello_world_container.builder @@ -1,3 +1,3 @@ xml.test do - render :partial => 'hello', :locals => { :xm => xml } -end
\ No newline at end of file + render partial: "hello", locals: { xm: xml } +end diff --git a/actionview/test/fixtures/actionpack/test/hello_xml_world.builder b/actionview/test/fixtures/actionpack/test/hello_xml_world.builder index e7081b89fe..d16bb6b5cb 100644 --- a/actionview/test/fixtures/actionpack/test/hello_xml_world.builder +++ b/actionview/test/fixtures/actionpack/test/hello_xml_world.builder @@ -8,4 +8,4 @@ xml.html do xml.p "monks" xml.p "wiseguys" end -end
\ No newline at end of file +end diff --git a/actionview/test/fixtures/actionpack/test/non_erb_block_content_for.builder b/actionview/test/fixtures/actionpack/test/non_erb_block_content_for.builder index d539a425a4..cd65da751b 100644 --- a/actionview/test/fixtures/actionpack/test/non_erb_block_content_for.builder +++ b/actionview/test/fixtures/actionpack/test/non_erb_block_content_for.builder @@ -1,4 +1,4 @@ content_for :title do - 'Putting stuff in the title!' + "Putting stuff in the title!" end xml << "Great stuff!" diff --git a/actionview/test/fixtures/comments/empty.html+grid.erb b/actionview/test/fixtures/comments/empty.html+grid.erb new file mode 100644 index 0000000000..dc3fa32a81 --- /dev/null +++ b/actionview/test/fixtures/comments/empty.html+grid.erb @@ -0,0 +1 @@ +<h1>No Comment</h1> diff --git a/actionview/test/fixtures/comments/empty.html.builder b/actionview/test/fixtures/comments/empty.html.builder index 2b0c7207a3..12d6fdd9a5 100644 --- a/actionview/test/fixtures/comments/empty.html.builder +++ b/actionview/test/fixtures/comments/empty.html.builder @@ -1 +1 @@ -xml.h1 'No Comment'
\ No newline at end of file +xml.h1 "No Comment" diff --git a/actionview/test/fixtures/test/_partial_with_variants.html+grid.erb b/actionview/test/fixtures/test/_partial_with_variants.html+grid.erb new file mode 100644 index 0000000000..225363c8c3 --- /dev/null +++ b/actionview/test/fixtures/test/_partial_with_variants.html+grid.erb @@ -0,0 +1 @@ +<h1>Partial with variants</h1> diff --git a/actionview/test/fixtures/test/hello.builder b/actionview/test/fixtures/test/hello.builder index a471553941..b8ab17ad5b 100644 --- a/actionview/test/fixtures/test/hello.builder +++ b/actionview/test/fixtures/test/hello.builder @@ -1,4 +1,4 @@ xml.html do xml.p "Hello #{@name}" - xml << render(:file => "test/greeting") -end
\ No newline at end of file + xml << render(file: "test/greeting") +end diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 07a6452cc1..6093a4e660 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -53,6 +53,7 @@ class AssetTagHelperTest < ActionView::TestCase %(auto_discovery_link_tag) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />), %(auto_discovery_link_tag(:rss)) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />), %(auto_discovery_link_tag(:atom)) => %(<link href="http://www.example.com" rel="alternate" title="ATOM" type="application/atom+xml" />), + %(auto_discovery_link_tag(:json)) => %(<link href="http://www.example.com" rel="alternate" title="JSON" type="application/json" />), %(auto_discovery_link_tag(:rss, :action => "feed")) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />), %(auto_discovery_link_tag(:rss, "http://localhost/feed")) => %(<link href="http://localhost/feed" rel="alternate" title="RSS" type="application/rss+xml" />), %(auto_discovery_link_tag(:rss, "//localhost/feed")) => %(<link href="//localhost/feed" rel="alternate" title="RSS" type="application/rss+xml" />), @@ -709,13 +710,13 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase def test_should_wildcard_asset_host @controller.config.asset_host = "http://a%d.example.com" - assert_match(%r(http://a[0123].example.com), compute_asset_host("foo")) + assert_match(%r(http://a[0123]\.example\.com), compute_asset_host("foo")) end def test_should_wildcard_asset_host_between_zero_and_four @controller.config.asset_host = "http://a%d.example.com" - assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path("xml.png")) - assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_url("xml.png")) + assert_match(%r(http://a[0123]\.example\.com/collaboration/hieraki/images/xml\.png), image_path("xml.png")) + assert_match(%r(http://a[0123]\.example\.com/collaboration/hieraki/images/xml\.png), image_url("xml.png")) end def test_asset_host_without_protocol_should_be_protocol_relative diff --git a/actionview/test/template/atom_feed_helper_test.rb b/actionview/test/template/atom_feed_helper_test.rb index 1245a1a966..7304b769a4 100644 --- a/actionview/test/template/atom_feed_helper_test.rb +++ b/actionview/test/template/atom_feed_helper_test.rb @@ -301,8 +301,8 @@ class AtomFeedTest < ActionController::TestCase with_restful_routing(:scrolls) do get :index, params: { id: "feed_with_atomPub_namespace" } assert_match %r{xml:lang="en-US"}, @response.body - assert_match %r{xmlns="http://www.w3.org/2005/Atom"}, @response.body - assert_match %r{xmlns:app="http://www.w3.org/2007/app"}, @response.body + assert_match %r{xmlns="http://www\.w3\.org/2005/Atom"}, @response.body + assert_match %r{xmlns:app="http://www\.w3\.org/2007/app"}, @response.body end end @@ -319,7 +319,7 @@ class AtomFeedTest < ActionController::TestCase with_restful_routing(:scrolls) do get :index, params: { id: "feed_with_xml_processing_instructions" } assert_match %r{<\?xml-stylesheet [^\?]*type="text/css"}, @response.body - assert_match %r{<\?xml-stylesheet [^\?]*href="t.css"}, @response.body + assert_match %r{<\?xml-stylesheet [^\?]*href="t\.css"}, @response.body end end @@ -334,7 +334,7 @@ class AtomFeedTest < ActionController::TestCase def test_feed_xhtml with_restful_routing(:scrolls) do get :index, params: { id: "feed_with_xhtml_content" } - assert_match %r{xmlns="http://www.w3.org/1999/xhtml"}, @response.body + assert_match %r{xmlns="http://www\.w3\.org/1999/xhtml"}, @response.body assert_select "summary", text: /Something Boring/ assert_select "summary", text: /after 2/ end diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb index d257147e1f..a259752d6a 100644 --- a/actionview/test/template/date_helper_test.rb +++ b/actionview/test/template/date_helper_test.rb @@ -128,6 +128,16 @@ class DateHelperTest < ActionView::TestCase assert_distance_of_time_in_words(from) end + def test_distance_in_words_with_nil_input + assert_raises(ArgumentError) { distance_of_time_in_words(nil) } + assert_raises(ArgumentError) { distance_of_time_in_words(0, nil) } + end + + def test_distance_in_words_with_mixed_argument_types + assert_equal "1 minute", distance_of_time_in_words(0, Time.at(60)) + assert_equal "10 minutes", distance_of_time_in_words(Time.at(600), 0) + end + def test_distance_in_words_with_mathn_required # test we avoid Integer#/ (redefined by mathn) silence_warnings { require "mathn" } diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb index a814cab686..de04f3f25d 100644 --- a/actionview/test/template/digestor_test.rb +++ b/actionview/test/template/digestor_test.rb @@ -14,7 +14,7 @@ class FixtureTemplate end class FixtureFinder < ActionView::LookupContext - FIXTURES_DIR = "#{File.dirname(__FILE__)}/../fixtures/digestor" + FIXTURES_DIR = File.expand_path("../fixtures/digestor", __dir__) def initialize(details = {}) super(ActionView::PathSet.new(["digestor", "digestor/api"]), details, []) @@ -122,13 +122,13 @@ class TemplateDigestorTest < ActionView::TestCase end def test_logging_of_missing_template_for_dependencies - assert_logged "'messages/something_missing' file doesn't exist, so no dependencies" do + assert_logged "Couldn't find template for digesting: messages/something_missing" do dependencies("messages/something_missing") end end def test_logging_of_missing_template_for_nested_dependencies - assert_logged "'messages/something_missing' file doesn't exist, so no dependencies" do + assert_logged "Couldn't find template for digesting: messages/something_missing" do nested_dependencies("messages/something_missing") 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 a4a2966ff9..bff0643fb0 100644 --- a/actionview/test/template/form_helper/form_with_test.rb +++ b/actionview/test/template/form_helper/form_with_test.rb @@ -302,6 +302,7 @@ class FormWithActsLikeFormForTest < FormWithTest concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) + concat f.select(:category, %w( animal economy sports )) concat f.submit("Create post") concat f.button("Create post") concat f.button { @@ -315,6 +316,7 @@ class FormWithActsLikeFormForTest < FormWithTest "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \ "<input name='post[secret]' type='hidden' value='0' />" \ "<input name='post[secret]' checked='checked' type='checkbox' value='1' />" \ + "<select name='post[category]'><option value='animal'>animal</option>\n<option value='economy'>economy</option>\n<option value='sports'>sports</option></select>" \ "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" \ "<button name='button' type='submit'>Create post</button>" \ "<button name='button' type='submit'><span>Create post</span></button>" @@ -729,6 +731,28 @@ class FormWithActsLikeFormForTest < FormWithTest assert_dom_equal expected, output_buffer end + def test_form_is_not_remote_by_default_if_form_with_generates_remote_forms_is_false + old_value = ActionView::Helpers::FormHelper.form_with_generates_remote_forms + ActionView::Helpers::FormHelper.form_with_generates_remote_forms = false + + form_with(model: @post, url: "/", id: "create-post", method: :patch) do |f| + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) + end + + expected = whole_form("/", "create-post", method: "patch", local: true) do + "<input name='post[title]' type='text' value='Hello World' />" \ + "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \ + "<input name='post[secret]' type='hidden' value='0' />" \ + "<input name='post[secret]' checked='checked' type='checkbox' value='1' />" + end + + assert_dom_equal expected, output_buffer + ensure + ActionView::Helpers::FormHelper.form_with_generates_remote_forms = old_value + end + def test_form_with_skip_enforcing_utf8_true form_with(scope: :post, skip_enforcing_utf8: true) do |f| concat f.text_field(:title) diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb index 258dcdb806..3247f20ba7 100644 --- a/actionview/test/template/form_options_helper_test.rb +++ b/actionview/test/template/form_options_helper_test.rb @@ -6,6 +6,15 @@ class Map < Hash end end +class CustomEnumerable + include Enumerable + + def each + yield "one" + yield "two" + end +end + class FormOptionsHelperTest < ActionView::TestCase tests ActionView::Helpers::FormOptionsHelper @@ -904,6 +913,14 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_select_with_enumerable + @post = Post.new + assert_dom_equal( + "<select id=\"post_category\" name=\"post[category]\"><option value=\"one\">one</option>\n<option value=\"two\">two</option></select>", + select("post", "category", CustomEnumerable.new) + ) + end + def test_collection_select @post = Post.new @post.author_name = "Babe" diff --git a/actionview/test/template/log_subscriber_test.rb b/actionview/test/template/log_subscriber_test.rb index 7f358add7e..584666d54b 100644 --- a/actionview/test/template/log_subscriber_test.rb +++ b/actionview/test/template/log_subscriber_test.rb @@ -39,7 +39,7 @@ class AVLogSubscriberTest < ActiveSupport::TestCase def set_view_cache_dependencies def @view.view_cache_dependencies; []; end - def @view.fragment_cache_key(*); "ahoy `controller` dependency"; end + def @view.combined_fragment_cache_key(*); "ahoy `controller` dependency"; end end def test_render_file_template diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index 412948719c..9999607067 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -10,8 +10,8 @@ module RenderTestCases @view = Class.new(ActionView::Base) do def view_cache_dependencies; end - def fragment_cache_key(key) - ActiveSupport::Cache.expand_cache_key(key, :views) + def combined_fragment_cache_key(key) + [ :views, key ] end end.new(paths, @assigns) @@ -27,7 +27,7 @@ module RenderTestCases def test_render_without_options e = assert_raises(ArgumentError) { @view.render() } - assert_match(/You invoked render but did not give any of (.+) option./, e.message) + assert_match(/You invoked render but did not give any of (.+) option\./, e.message) end def test_render_file @@ -83,6 +83,10 @@ module RenderTestCases assert_equal "<h1>Kein Kommentar</h1>", @view.render(template: "comments/empty", locale: [:de]) end + def test_render_template_with_variants + assert_equal "<h1>No Comment</h1>\n", @view.render(template: "comments/empty", variants: :grid) + end + def test_render_file_with_handlers assert_equal "<h1>No Comment</h1>\n", @view.render(file: "comments/empty", handlers: [:builder]) assert_equal "<h1>No Comment</h1>\n", @view.render(file: "comments/empty", handlers: :builder) @@ -134,7 +138,7 @@ module RenderTestCases end def test_render_file_with_full_path - template_path = File.join(File.dirname(__FILE__), "../fixtures/test/hello_world") + template_path = File.expand_path("../fixtures/test/hello_world", __dir__) assert_equal "Hello world!", @view.render(file: template_path) end @@ -156,7 +160,7 @@ module RenderTestCases end def test_render_outside_path - assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")) + assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__)) assert_raises ActionView::MissingTemplate do @view.render(template: "../\\../test/abstract_unit.rb") end @@ -170,6 +174,10 @@ module RenderTestCases assert_equal "partial html", @view.render(partial: "test/partial") end + def test_render_partial_with_variants + assert_equal "<h1>Partial with variants</h1>\n", @view.render(partial: "test/partial_with_variants", variants: :grid) + end + def test_render_partial_with_selected_format assert_equal "partial html", @view.render(partial: "test/partial", formats: :html) assert_equal "partial js", @view.render(partial: "test/partial", formats: [:js]) @@ -253,7 +261,7 @@ module RenderTestCases def test_render_sub_template_with_errors e = assert_raises(ActionView::Template::Error) { @view.render(template: "test/sub_template_raise") } assert_match %r!method.*doesnt_exist!, e.message - assert_match %r{Trace of template inclusion: .*test/sub_template_raise.html.erb}, e.sub_template_message + assert_match %r{Trace of template inclusion: .*test/sub_template_raise\.html\.erb}, e.sub_template_message assert_equal "1", e.line_number assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name end @@ -710,6 +718,6 @@ class CachedCollectionViewRenderTest < ActiveSupport::TestCase private def cache_key(*names, virtual_path) digest = ActionView::Digestor.digest name: virtual_path, finder: @view.lookup_context, dependencies: [] - @view.fragment_cache_key([ *names, digest ]) + @view.combined_fragment_cache_key([ "#{virtual_path}:#{digest}", *names ]) end end diff --git a/actionview/test/template/resolver_patterns_test.rb b/actionview/test/template/resolver_patterns_test.rb index 43e3f21076..8e21f4b828 100644 --- a/actionview/test/template/resolver_patterns_test.rb +++ b/actionview/test/template/resolver_patterns_test.rb @@ -2,7 +2,7 @@ require "abstract_unit" class ResolverPatternsTest < ActiveSupport::TestCase def setup - path = File.expand_path("../../fixtures/", __FILE__) + path = File.expand_path("../fixtures", __dir__) pattern = ":prefix/{:formats/,}:action{.:formats,}{+:variants,}{.:handlers,}" @resolver = ActionView::FileSystemResolver.new(path, pattern) end diff --git a/actionview/test/template/sanitize_helper_test.rb b/actionview/test/template/sanitize_helper_test.rb index 11ed55456f..4d4ed3c35c 100644 --- a/actionview/test/template/sanitize_helper_test.rb +++ b/actionview/test/template/sanitize_helper_test.rb @@ -1,6 +1,6 @@ require "abstract_unit" -# The exhaustive tests are in test/controller/html/sanitizer_test.rb. +# The exhaustive tests are in the rails-html-sanitizer gem. # This tests that the helpers hook up correctly to the sanitizer classes. class SanitizeHelperTest < ActionView::TestCase tests ActionView::Helpers::SanitizeHelper diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb index 09454b32cc..58d903b1c8 100644 --- a/actionview/test/template/url_helper_test.rb +++ b/actionview/test/template/url_helper_test.rb @@ -231,7 +231,11 @@ class UrlHelperTest < ActiveSupport::TestCase end def to_h - { foo: :bar, baz: "quux" } + if permitted? + { foo: :bar, baz: "quux" } + else + raise ArgumentError + end end end @@ -505,6 +509,12 @@ class UrlHelperTest < ActiveSupport::TestCase assert !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) + end + def test_current_page_with_params_that_match @request = request_for_url("/?order=desc&page=1") @@ -605,8 +615,8 @@ class UrlHelperTest < ActiveSupport::TestCase def test_mail_to_with_special_characters assert_dom_equal( - %{<a href="mailto:%23%21%24%25%26%27%2A%2B-%2F%3D%3F%5E_%60%7B%7D%7C%7E@example.org">#!$%&'*+-/=?^_`{}|~@example.org</a>}, - mail_to("#!$%&'*+-/=?^_`{}|~@example.org") + %{<a href="mailto:%23%21%24%25%26%27%2A%2B-%2F%3D%3F%5E_%60%7B%7D%7C@example.org">#!$%&'*+-/=?^_`{}|@example.org</a>}, + mail_to("#!$%&'*+-/=?^_`{}|@example.org") ) end @@ -672,13 +682,6 @@ class UrlHelperTest < ActiveSupport::TestCase def request_forgery_protection_token "form_token" end - - private - def sort_query_string_params(uri) - path, qs = uri.split("?") - qs = qs.split("&").sort.join("&") if qs - qs ? "#{path}?#{qs}" : path - end end class UrlHelperControllerTest < ActionController::TestCase @@ -876,6 +879,11 @@ class WorkshopsController < ActionController::Base @workshop = Workshop.new(params[:id]) render inline: "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>" end + + def edit + @workshop = Workshop.new(params[:id]) + render inline: "<%= current_page?(@workshop) %>" + end end class SessionsController < ActionController::Base @@ -940,4 +948,11 @@ class PolymorphicControllerTest < ActionController::TestCase get :edit, params: { workshop_id: 1, id: 1, format: "json" } assert_equal %{/workshops/1/sessions/1.json\n<a href="/workshops/1/sessions/1.json">Session</a>}, @response.body end + + def test_current_page_when_options_does_not_respond_to_to_hash + @controller = WorkshopsController.new + + get :edit, params: { id: 1 } + assert_equal "false", @response.body + end end diff --git a/actionview/test/ujs/config.ru b/actionview/test/ujs/config.ru index 48b7a4b53a..213a41127a 100644 --- a/actionview/test/ujs/config.ru +++ b/actionview/test/ujs/config.ru @@ -1,4 +1,4 @@ -$LOAD_PATH.unshift File.expand_path("..", __FILE__) +$LOAD_PATH.unshift __dir__ require "server" run UJS::Server diff --git a/actionview/test/ujs/public/test/call-remote.js b/actionview/test/ujs/public/test/call-remote.js index dbeb8ad832..5932195363 100644 --- a/actionview/test/ujs/public/test/call-remote.js +++ b/actionview/test/ujs/public/test/call-remote.js @@ -100,6 +100,34 @@ asyncTest('JS code should be executed', 1, function() { submit() }) +asyncTest('ecmascript code should be executed', 1, function() { + buildForm({ method: 'post', 'data-type': 'script' }) + + $('form').append('<input type="text" name="content_type" value="application/ecmascript">') + $('form').append('<input type="text" name="content" value="ok(true, \'remote code should be run\')">') + + submit() +}) + +asyncTest('execution of JS code does not modify current DOM', 1, function() { + var docLength, newDocLength + function getDocLength() { + return document.documentElement.outerHTML.length + } + + buildForm({ method: 'post', 'data-type': 'script' }) + + $('form').append('<input type="text" name="content_type" value="text/javascript">') + $('form').append('<input type="text" name="content" value="\'remote code should be run\'">') + + docLength = getDocLength() + + submit(function() { + newDocLength = getDocLength() + ok(docLength === newDocLength, 'executed JS should not present in the document') + }) +}) + asyncTest('XML document should be parsed', 1, function() { buildForm({ method: 'post', 'data-type': 'html' }) diff --git a/actionview/test/ujs/public/test/data-confirm.js b/actionview/test/ujs/public/test/data-confirm.js index 28190c2250..229b9e1466 100644 --- a/actionview/test/ujs/public/test/data-confirm.js +++ b/actionview/test/ujs/public/test/data-confirm.js @@ -26,6 +26,13 @@ module('data-confirm', { 'data-confirm': 'Are you absolutely sure?' })) + $('#qunit-fixture').append($('<button />', { + type: 'submit', + form: 'confirm', + disabled: 'disabled', + 'data-confirm': 'Are you absolutely sure?' + })) + this.windowConfirm = window.confirm }, teardown: function() { @@ -286,3 +293,24 @@ asyncTest('clicking on the children of a link should also trigger a confirm', 6, .find('strong') .triggerNative('click') }) + +asyncTest('clicking on the children of a disabled button should not trigger a confirm.', 1, function() { + var message + // auto-decline: + window.confirm = function(msg) { message = msg; return false } + + $('button[data-confirm][disabled]') + .html("<strong>Click me</strong>") + .bindNative('confirm', function() { + App.assertCallbackNotInvoked('confirm') + }) + .find('strong') + .bindNative('click', function() { + App.assertCallbackInvoked('click') + }) + .triggerNative('click') + + setTimeout(function() { + start() + }, 50) +}) diff --git a/actionview/test/ujs/public/test/data-remote.js b/actionview/test/ujs/public/test/data-remote.js index b756add24e..161a92ac11 100644 --- a/actionview/test/ujs/public/test/data-remote.js +++ b/actionview/test/ujs/public/test/data-remote.js @@ -1,3 +1,17 @@ +(function() { + +function buildSelect(attrs) { + attrs = $.extend({ + 'name': 'user_data', 'data-remote': 'true', 'data-url': '/echo', 'data-params': 'data1=value1' + }, attrs) + + $('#qunit-fixture').append( + $('<select />', attrs) + .append($('<option />', {value: 'optionValue1', text: 'option1'})) + .append($('<option />', {value: 'optionValue2', text: 'option2'})) + ) +} + module('data-remote', { setup: function() { $('#qunit-fixture') @@ -135,17 +149,7 @@ asyncTest('clicking on a button with data-remote attribute', 5, function() { }) asyncTest('changing a select option with data-remote attribute', 5, function() { - $('form') - .append( - $('<select />', { - 'name': 'user_data', - 'data-remote': 'true', - 'data-params': 'data1=value1', - 'data-url': '/echo' - }) - .append($('<option />', {value: 'optionValue1', text: 'option1'})) - .append($('<option />', {value: 'optionValue2', text: 'option2'})) - ) + buildSelect() $('select[data-remote]') .bindNative('ajax:success', function(e, data, status, xhr) { @@ -350,17 +354,7 @@ asyncTest('submitting a form with falsy "data-remote" attribute', 0, function() }) asyncTest('changing a select option with falsy "data-remote" attribute', 0, function() { - $('form') - .append( - $('<select />', { - 'name': 'user_data', - 'data-remote': 'false', - 'data-params': 'data1=value1', - 'data-url': '/echo' - }) - .append($('<option />', {value: 'optionValue1', text: 'option1'})) - .append($('<option />', {value: 'optionValue2', text: 'option2'})) - ) + buildSelect({'data-remote': 'false'}) $('select[data-remote=false]:first') .bindNative('ajax:beforeSend', function() { @@ -413,3 +407,32 @@ asyncTest('form buttons should only be serialized when clicked', 4, function() { }) .find('[name=submit2]').triggerNative('click') }) + +asyncTest('changing a select option without "data-url" attribute still fires ajax request to current location', 1, function() { + var currentLocation, ajaxLocation + + buildSelect({'data-url': ''}); + + $('select[data-remote]') + .bindNative('ajax:beforeSend', function(e, xhr, settings) { + // Get current location (the same way jQuery does) + try { + currentLocation = location.href + } catch(err) { + currentLocation = document.createElement( 'a' ) + currentLocation.href = '' + currentLocation = currentLocation.href + } + + ajaxLocation = settings.url.replace(settings.data, '').replace(/&$/, '').replace(/\?$/, '') + equal(ajaxLocation, currentLocation, 'URL should be current page by default') + + return false + }) + .val('optionValue2') + .triggerNative('change') + + setTimeout(function() { start() }, 20) +}) + +})() |