diff options
Diffstat (limited to 'actionview')
79 files changed, 1131 insertions, 639 deletions
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 959509510e..a0e773e887 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -2,213 +2,20 @@ *Vasiliy Ermolovich* -* Ensure ActionView::Digestor.cache is correctly cleaned up when - combining recursive templates with ActionView::Resolver.caching = false +* Fixed a problem where the default options for the `button_tag` helper is not + applied correctly. - *wyaeld* + Fixes #14254. -* Fix `collection_check_boxes` generated hidden input to use the name attribute provided - in the options hash. + *Sergey Prikhodko* - *Angel N. Sciortino* +* Take variants into account when calculating template digests in ActionView::Digestor. -* Fix some edge cases for AV `select` helper with `:selected` option + The arguments to ActionView::Digestor#digest are now being passed as a hash + to support variants and allow more flexibility in the future. The support for + regular (required) arguments is deprecated and will be removed in Rails 5.0 or later. - *Bogdan Gusiev* + *Piotr Chmolowski, Łukasz Strzałkowski* -* Ability to pass block to `select` helper - <%= select(report, "campaign_ids") do %> - <% available_campaigns.each do |c| -%> - <%= content_tag(:option, c.name, value: c.id, data: { tags: c.tags.to_json }) %> - <% end -%> - <% end -%> - - *Bogdan Gusiev* - -* Handle `:namespace` form option in collection labels - - *Vasiliy Ermolovich* - -* Fix `form_for` when both `namespace` and `as` options are present - - `as` option no longer overwrites `namespace` option when generating - html id attribute of the form element - - *Adam Niedzielski* - -* Fix `excerpt` when `:separator` is `nil`. - - *Paul Nikitochkin* - -* Only cache template digests if `config.cache_template_loading` id true. - - *Josh Lauer*, *Justin Ridgewell* - -* Fixed a bug where the lookup details were not being taken into account - when caching the digest of a template - changes to the details now - cause a different cache key to be used. - - *Daniel Schierbeck* - -* Added an `extname` hash option for `javascript_include_tag` method. - - Before: - - javascript_include_tag('templates.jst') - # => <script src="/javascripts/templates.jst.js"></script> - - After: - - javascript_include_tag('templates.jst', extname: false ) - # => <script src="/javascripts/templates.jst"></script> - - *Nathan Stitt* - -* Fix `current_page?` when the URL contains escaped characters and the - original URL is using the hexadecimal lowercased. - - *Rafael Mendonça França* - -* Fix `text_area` to behave like `text_field` when `nil` is given as - value. - - Before: - - f.text_field :field, value: nil #=> <input value=""> - f.text_area :field, value: nil #=> <textarea>value of field</textarea> - - After: - - f.text_area :field, value: nil #=> <textarea></textarea> - - *Joel Cogen* - -* Element of the `grouped_options_for_select` can - optionally contain html attributes as the last element of the array. - - grouped_options_for_select( - [["North America", [['United States','US'],"Canada"], data: { foo: 'bar' }]] - ) - - *Vasiliy Ermolovich* - -* Fix default rendered format problem when calling `render` without :content_type option. - It should return :html. Fix #11393. - - *Gleb Mazovetskiy* *Oleg* *kennyj* - -* Fix `link_to` with block and url hashes. - - Before: - - link_to(action: 'bar', controller: 'foo') { content_tag(:span, 'Example site') } - # => "<a action=\"bar\" controller=\"foo\"><span>Example site</span></a>" - - After: - - link_to(action: 'bar', controller: 'foo') { content_tag(:span, 'Example site') } - # => "<a href=\"/foo/bar\"><span>Example site</span></a>" - - *Murahashi Sanemat Kenichi* - -* Fix "Stack Level Too Deep" error when redering recursive partials. - - Fixes #11340. - - *Rafael Mendonça França* - -* Added an `enforce_utf8` hash option for `form_tag` method. - - Control to output a hidden input tag with name `utf8` without monkey - patching. - - Before: - - form_tag - # => '<form>..<input name="utf8" type="hidden" value="✓" />..</form>' - - After: - - form_tag - # => '<form>..<input name="utf8" type="hidden" value="✓" />..</form>' - - form_tag({}, { :enforce_utf8 => false }) - # => '<form>....</form>' - - *ma2gedev* - -* Remove the deprecated `include_seconds` argument from `distance_of_time_in_words`, - pass in an `:include_seconds` hash option to use this feature. - - *Carlos Antonio da Silva* - -* Remove deprecated block passing to `FormBuilder#new`. - - *Vipul A M* - -* Pick `DateField` `DateTimeField` and `ColorField` values from stringified options allowing use of symbol keys with helpers. - - *Jon Rowe* - -* Remove the deprecated `prompt` argument from `grouped_options_for_select`, - pass in a `:prompt` hash option to use this feature. - - *kennyj* - -* Always escape the result of `link_to_unless` method. - - Before: - - link_to_unless(true, '<b>Showing</b>', 'github.com') - # => "<b>Showing</b>" - - After: - - link_to_unless(true, '<b>Showing</b>', 'github.com') - # => "<b>Showing</b>" - - *dtaniwaki* - -* Use a case insensitive URI Regexp for #asset_path. - - This fix a problem where the same asset path using different case are generating - different URIs. - - Before: - - image_tag("HTTP://google.com") - # => "<img alt=\"Google\" src=\"/assets/HTTP://google.com\" />" - image_tag("http://google.com") - # => "<img alt=\"Google\" src=\"http://google.com\" />" - - After: - - image_tag("HTTP://google.com") - # => "<img alt=\"Google\" src=\"HTTP://google.com\" />" - image_tag("http://google.com") - # => "<img alt=\"Google\" src=\"http://google.com\" />" - - *David Celis* - -* Element of the `collection_check_boxes` and `collection_radio_buttons` can - optionally contain html attributes as the last element of the array. - - *Vasiliy Ermolovich* - -* Update the HTML `BOOLEAN_ATTRIBUTES` in `ActionView::Helpers::TagHelper` - to conform to the latest HTML 5.1 spec. Add attributes `allowfullscreen`, - `default`, `inert`, `sortable`, `truespeed`, `typemustmatch`. Fix attribute - `seamless` (previously misspelled `seemless`). - - *Alex Peattie* - -* Fix an issue where partials with a number in the filename weren't being digested for cache dependencies. - - *Bryan Ricker* - -* First release, ActionView extracted from ActionPack - - *Piotr Sarnacki*, *Łukasz Strzałkowski* - -Please check [4-0-stable (ActionPack's CHANGELOG)](https://github.com/rails/rails/blob/4-0-stable/actionpack/CHANGELOG.md) for previous changes. +Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionview/CHANGELOG.md) for previous changes. diff --git a/actionview/MIT-LICENSE b/actionview/MIT-LICENSE index 5c668d9624..d58dd9ed9b 100644 --- a/actionview/MIT-LICENSE +++ b/actionview/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2013 David Heinemeier Hansson +Copyright (c) 2004-2014 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec index cdac074973..e45dd04225 100644 --- a/actionview/actionview.gemspec +++ b/actionview/actionview.gemspec @@ -20,10 +20,10 @@ Gem::Specification.new do |s| s.requirements << 'none' s.add_dependency 'activesupport', version - s.add_dependency 'activemodel', version - s.add_dependency 'builder', '~> 3.1.0' + s.add_dependency 'builder', '~> 3.1' s.add_dependency 'erubis', '~> 2.7.0' - s.add_development_dependency 'actionpack', version + s.add_development_dependency 'actionpack', version + s.add_development_dependency 'activemodel', version end diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb index 39c0c6c856..50712e0830 100644 --- a/actionview/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2013 David Heinemeier Hansson +# Copyright (c) 2004-2014 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -23,10 +23,13 @@ require 'active_support' require 'active_support/rails' +require 'action_view/version' module ActionView extend ActiveSupport::Autoload + ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' + eager_autoload do autoload :Base autoload :Context @@ -53,7 +56,6 @@ module ActionView autoload_at "action_view/template/resolver" do autoload :Resolver autoload :PathResolver - autoload :FileSystemResolver autoload :OptimizedFileSystemResolver autoload :FallbackFileSystemResolver end @@ -80,10 +82,9 @@ module ActionView autoload :TestCase - ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' - def self.eager_load! super + ActionView::Helpers.eager_load! ActionView::Template.eager_load! HTML.eager_load! end diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb index caade8f43b..455ce531ae 100644 --- a/actionview/lib/action_view/base.rb +++ b/actionview/lib/action_view/base.rb @@ -1,5 +1,5 @@ require 'active_support/core_ext/module/attr_internal' -require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/module/attribute_accessors' require 'active_support/ordered_options' require 'action_view/log_subscriber' require 'action_view/helpers' @@ -153,6 +153,10 @@ module ActionView #:nodoc: # Specify default_formats that can be rendered. cattr_accessor :default_formats + # Specify whether an error should be raised for missing translations + cattr_accessor :raise_on_missing_translations + @@raise_on_missing_translations = false + class_attribute :_routes class_attribute :logger diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb index b2e8334077..0ccf2515c5 100644 --- a/actionview/lib/action_view/dependency_tracker.rb +++ b/actionview/lib/action_view/dependency_tracker.rb @@ -1,7 +1,7 @@ require 'thread_safe' module ActionView - class DependencyTracker + class DependencyTracker # :nodoc: @trackers = ThreadSafe::Cache.new def self.find_dependencies(name, template) @@ -23,24 +23,52 @@ module ActionView @trackers.delete(handler) end - class ERBTracker + class ERBTracker # :nodoc: EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/ + # A valid ruby identifier - suitable for class, method and specially variable names + IDENTIFIER = / + [[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore + [[:word:]]* # followed by optional letters, numbers or underscores + /x + + # Any kind of variable name. e.g. @instance, @@class, $global or local. + # Possibly following a method call chain + VARIABLE_OR_METHOD_CHAIN = / + (?:\$|@{1,2})? # optional global, instance or class variable indicator + (?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls + (?<dynamic>#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC + /x + + # A simple string literal. e.g. "School's out!" + STRING = / + (?<quote>['"]) # an opening quote + (?<static>.*?) # with anything inside, captured as STATIC + \k<quote> # and a matching closing quote + /x + + # Part of any hash containing the :partial key + PARTIAL_HASH_KEY = / + (?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax + \s* # followed by optional spaces + /x + # Matches: - # render partial: "comments/comment", collection: commentable.comments - # render "comments/comments" - # render 'comments/comments' - # render('comments/comments') + # partial: "comments/comment", collection: @all_comments => "comments/comment" + # (object: @single_comment, partial: "comments/comment") => "comments/comment" # - # render(@topic) => render("topics/topic") - # render(topics) => render("topics/topic") - # render(message.topics) => render("topics/topic") - RENDER_DEPENDENCY = / - render\s* # render, followed by optional whitespace - \(? # start an optional parenthesis for the render call - (partial:|:partial\s+=>)?\s* # naming the partial, used with collection -- 1st capture - ([@a-z"'][@\w\/\."']+) # the template name itself -- 2nd capture - /x + # "comments/comments" + # 'comments/comments' + # ('comments/comments') + # + # (@topic) => "topics/topic" + # topics => "topics/topic" + # (message.topics) => "topics/topic" + RENDER_ARGUMENTS = /\A + (?:\s*\(?\s*) # optional opening paren surrounded by spaces + (?:.*?#{PARTIAL_HASH_KEY})? # optional hash, up to the partial key declaration + (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest + /xm def self.call(name, template) new(name, template).dependencies @@ -68,19 +96,33 @@ module ActionView end def render_dependencies - source.scan(RENDER_DEPENDENCY). - collect(&:second).uniq. + render_dependencies = [] + render_calls = source.split(/\brender\b/).drop(1) - # render(@topic) => render("topics/topic") - # render(topics) => render("topics/topic") - # render(message.topics) => render("topics/topic") - collect { |name| name.sub(/\A@?([a-z_]+\.)*([a-z_]+)\z/) { "#{$2.pluralize}/#{$2.singularize}" } }. + render_calls.each do |arguments| + arguments.scan(RENDER_ARGUMENTS) do + add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic]) + add_static_dependency(render_dependencies, Regexp.last_match[:static]) + end + end - # render("headline") => render("message/headline") - collect { |name| name.include?("/") ? name : "#{directory}/#{name}" }. + render_dependencies.uniq + end + + def add_dynamic_dependency(dependencies, dependency) + if dependency + dependencies << "#{dependency.pluralize}/#{dependency.singularize}" + end + end - # replace quotes from string renders - collect { |name| name.gsub(/["']/, "") } + def add_static_dependency(dependencies, dependency) + if dependency + if dependency.include?('/') + dependencies << dependency + else + dependencies << "#{directory}/#{dependency}" + end + end end def explicit_dependencies diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 5570e2a8dc..40d493da64 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -9,23 +9,53 @@ module ActionView @@digest_monitor = Monitor.new class << self - def digest(name, format, finder, options = {}) - details_key = finder.details_key.hash + # Supported options: + # + # * <tt>name</tt> - Template name + # * <tt>format</tt> - Template format + # * <tt>variant</tt> - Variant of +format+ (optional) + # * <tt>finder</tt> - An instance of ActionView::LookupContext + # * <tt>dependencies</tt> - An array of dependent views + # * <tt>partial</tt> - Specifies whether the template is a partial + def digest(options_or_deprecated_name, *deprecated_args) + options = _options_for_digest(options_or_deprecated_name, *deprecated_args) + + details_key = options[:finder].details_key.hash dependencies = Array.wrap(options[:dependencies]) - cache_key = ([name, details_key, format] + dependencies).join('.') + cache_key = ([options[:name], details_key, options[:format], options[:variant]].compact + dependencies).join('.') # this is a correctly done double-checked locking idiom # (ThreadSafe::Cache's lookups have volatile semantics) @@cache[cache_key] || @@digest_monitor.synchronize do @@cache.fetch(cache_key) do # re-check under lock - compute_and_store_digest(cache_key, name, format, finder, options) + compute_and_store_digest(cache_key, options) end end end + def _options_for_digest(options_or_deprecated_name, *deprecated_args) + if !options_or_deprecated_name.is_a?(Hash) + ActiveSupport::Deprecation.warn \ + 'ActionView::Digestor.digest changed its method signature from ' \ + '#digest(name, format, finder, options) to #digest(options), ' \ + 'with options now including :name, :format, :finder, and ' \ + ':variant. Please update your code to pass a Hash argument. ' \ + 'Support for the old method signature will be removed in Rails 5.0.' + + _options_for_digest (deprecated_args[2] || {}).merge \ + name: options_or_deprecated_name, + format: deprecated_args[0], + finder: deprecated_args[1] + else + options_or_deprecated_name.assert_valid_keys(:name, :format, :variant, :finder, :dependencies, :partial) + options_or_deprecated_name + end + end + private - def compute_and_store_digest(cache_key, name, format, finder, options) # called under @@digest_monitor lock - klass = if options[:partial] || name.include?("/_") + + def compute_and_store_digest(cache_key, options) # called under @@digest_monitor lock + klass = if options[:partial] || options[:name].include?("/_") # Prevent re-entry or else recursive templates will blow the stack. # There is no need to worry about other threads seeing the +false+ value, # as they will then have to wait for this thread to let go of the @@digest_monitor lock. @@ -35,20 +65,22 @@ module ActionView Digestor end - digest = klass.new(name, format, finder, options).digest + digest = klass.new(options).digest # Store the actual digest if config.cache_template_loading is true @@cache[cache_key] = stored_digest = digest if ActionView::Resolver.caching? digest ensure # something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache - @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest + @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest end end - attr_reader :name, :format, :finder, :options + attr_reader :name, :format, :variant, :finder, :options - def initialize(name, format, finder, options={}) - @name, @format, @finder, @options = name, format, finder, options + def initialize(options_or_deprecated_name, *deprecated_args) + options = self.class._options_for_digest(options_or_deprecated_name, *deprecated_args) + @options = options.except(:name, :format, :variant, :finder) + @name, @format, @variant, @finder = options.values_at(:name, :format, :variant, :finder) end def digest @@ -68,7 +100,7 @@ module ActionView def nested_dependencies dependencies.collect do |dependency| - dependencies = PartialDigestor.new(dependency, format, finder).nested_dependencies + dependencies = PartialDigestor.new(name: dependency, format: format, finder: finder).nested_dependencies dependencies.any? ? { dependency => dependencies } : dependency end end @@ -88,7 +120,11 @@ module ActionView end def template - @template ||= finder.find(logical_name, [], partial?, formats: [ format ]) + @template ||= begin + finder.disable_cache do + finder.find(logical_name, [], partial?, [], formats: [format], variants: [variant]) + end + end end def source @@ -97,7 +133,7 @@ module ActionView def dependency_digest template_digests = dependencies.collect do |template_name| - Digestor.digest(template_name, format, finder, partial: true) + Digestor.digest(name: template_name, format: format, finder: finder, partial: true) end (template_digests + injected_dependencies).join("-") diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb new file mode 100644 index 0000000000..9266e55c47 --- /dev/null +++ b/actionview/lib/action_view/gem_version.rb @@ -0,0 +1,15 @@ +module ActionView + # Returns the version of the currently loaded ActionView as a <tt>Gem::Version</tt> + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 4 + MINOR = 2 + TINY = 0 + PRE = "alpha" + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/actionview/lib/action_view/helpers.rb b/actionview/lib/action_view/helpers.rb index 8a78685ae1..787e9d67b2 100644 --- a/actionview/lib/action_view/helpers.rb +++ b/actionview/lib/action_view/helpers.rb @@ -27,6 +27,12 @@ module ActionView #:nodoc: autoload :TextHelper autoload :TranslationHelper autoload :UrlHelper + autoload :Tags + + def self.eager_load! + super + Tags.eager_load! + end extend ActiveSupport::Concern diff --git a/actionview/lib/action_view/helpers/active_model_helper.rb b/actionview/lib/action_view/helpers/active_model_helper.rb index 901f433c70..d5222e3616 100644 --- a/actionview/lib/action_view/helpers/active_model_helper.rb +++ b/actionview/lib/action_view/helpers/active_model_helper.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/enumerable' module ActionView diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index e2430140c2..aa49f1edc1 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -103,7 +103,7 @@ module ActionView }.join("\n").html_safe end - # Returns a link tag that browsers and news readers can use to auto-detect + # 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+. @@ -153,14 +153,14 @@ module ActionView # # ==== Examples # - # favicon_link_tag '/myicon.ico' + # favicon_link_tag 'myicon.ico' # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" /> # # Mobile Safari looks for a different <link> tag, pointing to an image that # will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad. # The following call would generate such a tag: # - # favicon_link_tag '/mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' + # favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' # # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" /> def favicon_link_tag(source='favicon.ico', options={}) tag('link', { @@ -176,7 +176,7 @@ module ActionView # ==== Options # # You can add HTML attributes using the +options+. The +options+ supports - # three additional keys for convenience and conformance: + # two additional keys for convenience and conformance: # # * <tt>:alt</tt> - If no alt text is given, the file name part of the # +source+ is used (capitalized and without the extension) @@ -207,11 +207,7 @@ module ActionView options[:alt] = options.fetch(:alt){ image_alt(src) } end - if size = options.delete(:size) - options[:width], options[:height] = size.split("x") if size =~ %r{\A\d+x\d+\z} - options[:width] = options[:height] = size if size =~ %r{\A\d+\z} - end - + options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size] tag("img", options) end @@ -248,9 +244,9 @@ module ActionView # # * <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+. - # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes - # width="30" and height="45". <tt>:size</tt> will be ignored if the - # value is not in the correct format. + # * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes + # width="30" and height="45", and "50" becomes width="50" and height="50". + # <tt>:size</tt> will be ignored if the value is not in the correct format. # # ==== Examples # @@ -264,6 +260,8 @@ module ActionView # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" /> # video_tag("/trailers/hd.avi", size: "16x16") # # => <video src="/trailers/hd.avi" width="16" height="16" /> + # video_tag("/trailers/hd.avi", size: "16") + # # => <video height="16" src="/trailers/hd.avi" width="16" /> # video_tag("/trailers/hd.avi", height: '32', width: '32') # # => <video height="32" src="/trailers/hd.avi" width="32" /> # video_tag("trailer.ogg", "trailer.flv") @@ -275,10 +273,7 @@ module ActionView def video_tag(*sources) multiple_sources_tag('video', sources) do |options| options[:poster] = path_to_image(options[:poster]) if options[:poster] - - if size = options.delete(:size) - options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$} - end + options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size] end end @@ -314,6 +309,14 @@ module ActionView content_tag(type, nil, options) end end + + def extract_dimensions(size) + if size =~ %r{\A\d+x\d+\z} + size.split('x') + elsif size =~ %r{\A\d+\z} + [size, size] + end + end end end end diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb index 42b1dd8933..af70a4242a 100644 --- a/actionview/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb @@ -64,7 +64,7 @@ module ActionView # 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed| # feed.title("My great blog!") # feed.updated((@posts.first.created_at)) - # feed.tag!(openSearch:totalResults, 10) + # feed.tag!('openSearch:totalResults', 10) # # @posts.each do |post| # feed.entry(post) do |entry| diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index b3af1d4da4..3177d18c4d 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -165,10 +165,20 @@ module ActionView def fragment_name_with_digest(name) #:nodoc: if @virtual_path - [ - *Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name), - Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context, dependencies: view_cache_dependencies) - ] + variant = request.variant.is_a?(Array) ? request.variant.first : request.variant + + options = { + name: @virtual_path, + format: formats.last.to_sym, + variant: variant, + finder: lookup_context, + dependencies: view_cache_dependencies + } + + names = Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name) + digest = Digestor.digest(options) + + [*names, digest] else name end diff --git a/actionview/lib/action_view/helpers/csrf_helper.rb b/actionview/lib/action_view/helpers/csrf_helper.rb index eeb0ed94b9..5af92c4ff2 100644 --- a/actionview/lib/action_view/helpers/csrf_helper.rb +++ b/actionview/lib/action_view/helpers/csrf_helper.rb @@ -12,8 +12,11 @@ module ActionView # These are used to generate the dynamic forms that implement non-remote links with # <tt>:method</tt>. # - # Note that regular forms generate hidden fields, and that Ajax calls are whitelisted, - # so they do not use these tags. + # You don't need to use these tags for regular forms as they generate their own hidden fields. + # + # For AJAX requests other than GETs, extract the "csrf-token" from the meta-tag and send as the + # "X-CSRF-Token" HTTP header. If you are using jQuery with jquery-rails this happens automatically. + # def csrf_meta_tags if protect_against_forgery? [ diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index d7e8e99200..1738df9cac 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -19,6 +19,10 @@ module ActionView # the <tt>select_month</tt> method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead # of \date[month]. module DateHelper + MINUTES_IN_YEAR = 525600 + MINUTES_IN_QUARTER_YEAR = 131400 + MINUTES_IN_THREE_QUARTERS_YEAR = 394200 + # Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds. # Pass <tt>include_seconds: true</tt> if you want more detailed approximations when distance < 1 min, 29 secs. # Distances are reported based on the following table: @@ -50,19 +54,19 @@ module ActionView # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute - # distance_of_time_in_words(from_time, from_time + 15.seconds, include_seconds: true) # => less than 20 seconds + # distance_of_time_in_words(from_time, from_time + 15.seconds, include_seconds: true) # => less than 20 seconds # distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years # distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days - # distance_of_time_in_words(from_time, from_time + 45.seconds, include_seconds: true) # => less than a minute - # distance_of_time_in_words(from_time, from_time - 45.seconds, include_seconds: true) # => less than a minute + # distance_of_time_in_words(from_time, from_time + 45.seconds, include_seconds: true) # => less than a minute + # distance_of_time_in_words(from_time, from_time - 45.seconds, include_seconds: true) # => less than a minute # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year # distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years # distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => about 4 years # # to_time = Time.now + 6.years + 19.days - # distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years - # distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years + # distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years + # distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years # distance_of_time_in_words(Time.now, Time.now) # => less than a minute def distance_of_time_in_words(from_time, to_time = 0, options = {}) options = { @@ -115,16 +119,16 @@ module ActionView # 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. + # 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 - remainder = (minutes_with_offset % 525600) - distance_in_years = (minutes_with_offset.div 525600) - if remainder < 131400 + remainder = (minutes_with_offset % MINUTES_IN_YEAR) + distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR) + if remainder < MINUTES_IN_QUARTER_YEAR locale.t(:about_x_years, :count => distance_in_years) - elsif remainder < 394200 + elsif remainder < MINUTES_IN_THREE_QUARTERS_YEAR locale.t(:over_x_years, :count => distance_in_years) else locale.t(:almost_x_years, :count => distance_in_years + 1) @@ -169,9 +173,16 @@ module ActionView # "2 - February" instead of "February"). # * <tt>:use_month_names</tt> - Set to an array with 12 month names if you want to customize month names. # Note: You can also use Rails' i18n functionality for this. + # * <tt>:month_format_string</tt> - Set to a format string. The string gets passed keys +:number+ (integer) + # and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example. + # See <tt>Kernel.sprintf</tt> for documentation on format sequences. # * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing). - # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Time.now.year - 5</tt>. - # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Time.now.year + 5</tt>. + # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt>if + # you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to + # the current selected year minus 5. + # * <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>: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. @@ -846,24 +857,36 @@ module ActionView I18n.translate(key, :locale => @options[:locale]) end - # Lookup month name for number. - # month_name(1) => "January" + # Looks up month names by number (1-based): + # + # month_name(1) # => "January" + # + # If the <tt>:use_month_numbers</tt> option is passed: + # + # month_name(1) # => 1 + # + # If the <tt>:use_two_month_numbers</tt> option is passed: + # + # month_name(1) # => '01' + # + # If the <tt>:add_month_numbers</tt> option is passed: + # + # month_name(1) # => "1 - January" # - # If <tt>:use_month_numbers</tt> option is passed - # month_name(1) => 1 + # If the <tt>:month_format_string</tt> option is passed: # - # If <tt>:use_two_month_numbers</tt> option is passed - # month_name(1) => '01' + # month_name(1) # => "January (01)" # - # If <tt>:add_month_numbers</tt> option is passed - # month_name(1) => "1 - January" + # depending on the format string. def month_name(number) if @options[:use_month_numbers] number elsif @options[:use_two_digit_numbers] - sprintf "%02d", number + '%02d' % number elsif @options[:add_month_numbers] "#{number} - #{month_names[number]}" + elsif format_string = @options[:month_format_string] + format_string % {number: number, name: month_names[number]} else month_names[number] end @@ -1062,7 +1085,7 @@ module ActionView # Wraps ActionView::Helpers::DateHelper#datetime_select for form builders: # # <%= form_for @person do |f| %> - # <%= f.time_select :last_request_at %> + # <%= f.datetime_select :last_request_at %> # <%= f.submit %> # <% end %> # diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index 38d969ed0c..1ff090f244 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -3,9 +3,8 @@ require 'action_view/helpers/date_helper' require 'action_view/helpers/tag_helper' require 'action_view/helpers/form_tag_helper' require 'action_view/helpers/active_model_helper' -require 'action_view/helpers/tags' require 'action_view/model_naming' -require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/string/inflections' @@ -52,7 +51,7 @@ module ActionView # The HTML generated for this would be (modulus formatting): # # <form action="/people" class="new_person" id="new_person" method="post"> - # <div style="margin:0;padding:0;display:inline"> + # <div style="display:none"> # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" /> # </div> # <label for="person_first_name">First name</label>: @@ -82,7 +81,7 @@ module ActionView # the code above as is would yield instead: # # <form action="/people/256" class="edit_person" id="edit_person_256" method="post"> - # <div style="margin:0;padding:0;display:inline"> + # <div style="display:none"> # <input name="_method" type="hidden" value="patch" /> # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" /> # </div> @@ -316,7 +315,7 @@ module ActionView # The HTML generated for this would be: # # <form action='http://www.example.com' method='post' data-remote='true'> - # <div style='margin:0;padding:0;display:inline'> + # <div style='display:none'> # <input name='_method' type='hidden' value='patch' /> # </div> # ... @@ -334,7 +333,7 @@ module ActionView # The HTML generated for this would be: # # <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'> - # <div style='margin:0;padding:0;display:inline'> + # <div style='display:none'> # <input name='_method' type='hidden' value='patch' /> # </div> # ... @@ -458,7 +457,7 @@ module ActionView # doesn't create the form tags themselves. This makes fields_for suitable # for specifying additional model objects in the same form. # - # Although the usage and purpose of +field_for+ is similar to +form_for+'s, + # Although the usage and purpose of +fields_for+ is similar to +form_for+'s, # its method signature is slightly different. Like +form_for+, it yields # a FormBuilder object associated with a particular model object to a block, # and within the block allows methods to be called on the builder to @@ -763,8 +762,8 @@ module ActionView # text_field(:post, :title, class: "create_input") # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" /> # - # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login can not be admin!'); }") - # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login can not be admin!'); }"/> + # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }") + # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }"/> # # text_field(:snippet, :code, size: 20, class: 'code_input') # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" /> @@ -1269,7 +1268,7 @@ module ActionView # doesn't create the form tags themselves. This makes fields_for suitable # for specifying additional model objects in the same form. # - # Although the usage and purpose of +field_for+ is similar to +form_for+'s, + # Although the usage and purpose of +fields_for+ is similar to +form_for+'s, # its method signature is slightly different. Like +form_for+, it yields # a FormBuilder object associated with a particular model object to a block, # and within the block allows methods to be called on the builder to diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb index 4347983bad..f625a9ff49 100644 --- a/actionview/lib/action_view/helpers/form_options_helper.rb +++ b/actionview/lib/action_view/helpers/form_options_helper.rb @@ -260,7 +260,7 @@ module ActionView Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render end - # Return select and option tags for the given object and method, using + # Returns select and option tags for the given object and method, using # #time_zone_options_for_select to generate the list of option tags. # # In addition to the <tt>:include_blank</tt> option documented above, diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 142c27ace0..0bbe08166b 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -465,17 +465,23 @@ module ActionView # # <strong>Ask me!</strong> # # </button> # - # button_tag "Checkout", data: { disable_with => "Please wait..." } + # button_tag "Checkout", data: { disable_with: "Please wait..." } # # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button> # def button_tag(content_or_options = nil, options = nil, &block) - options = content_or_options if block_given? && content_or_options.is_a?(Hash) - options ||= {} - options = options.stringify_keys + if content_or_options.is_a? Hash + options = content_or_options + else + options ||= {} + end - options.reverse_merge! 'name' => 'button', 'type' => 'submit' + options = { 'name' => 'button', 'type' => 'submit' }.merge!(options.stringify_keys) - content_tag :button, content_or_options || 'Button', options, &block + if block_given? + content_tag :button, options, &block + else + content_tag :button, content_or_options || 'Button', options + end end # Displays an image which when clicked will submit the form. @@ -722,7 +728,7 @@ module ActionView enforce_utf8 = html_options.delete("enforce_utf8") { true } tags = (enforce_utf8 ? utf8_enforcer_tag : ''.html_safe) << method_tag - content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline') + content_tag(:div, tags, :style => 'display:none') end def form_tag_html(html_options) diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb index fda7038a5d..7157a95146 100644 --- a/actionview/lib/action_view/helpers/number_helper.rb +++ b/actionview/lib/action_view/helpers/number_helper.rb @@ -100,17 +100,12 @@ module ActionView # # number_to_currency(-1234567890.50, negative_format: "(%u%n)") # # => ($1,234,567,890.50) - # number_to_currency(1234567890.50, unit: "£", separator: ",", delimiter: "") - # # => £1234567890,50 - # number_to_currency(1234567890.50, unit: "£", separator: ",", delimiter: "", format: "%n %u") - # # => 1234567890,50 £ + # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "") + # # => R$1234567890,50 + # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u") + # # => 1234567890,50 R$ def number_to_currency(number, options = {}) - return unless number - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_currency(number, options) - } + delegate_number_helper_method(:number_to_currency, number, options) end # Formats a +number+ as a percentage string (e.g., 65%). You can @@ -150,12 +145,7 @@ module ActionView # # number_to_percentage("98a", raise: true) # => InvalidNumberError def number_to_percentage(number, options = {}) - return unless number - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_percentage(number, options) - } + delegate_number_helper_method(:number_to_percentage, number, options) end # Formats a +number+ with grouped thousands using +delimiter+ @@ -188,11 +178,7 @@ module ActionView # # number_with_delimiter("112a", raise: true) # => raise InvalidNumberError def number_with_delimiter(number, options = {}) - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_delimited(number, options) - } + delegate_number_helper_method(:number_to_delimited, number, options) end # Formats a +number+ with the specified level of @@ -237,11 +223,7 @@ module ActionView # number_with_precision(1111.2345, precision: 2, separator: ',', delimiter: '.') # # => 1.111,23 def number_with_precision(number, options = {}) - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_rounded(number, options) - } + delegate_number_helper_method(:number_to_rounded, number, options) end # Formats the bytes in +number+ into a more understandable @@ -293,11 +275,7 @@ module ActionView # number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB" # number_to_human_size(524288000, precision: 5) # => "500 MB" def number_to_human_size(number, options = {}) - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_human_size(number, options) - } + delegate_number_helper_method(:number_to_human_size, number, options) end # Pretty prints (formats and approximates) a number in a way it @@ -399,21 +377,36 @@ module ActionView # number_to_human(0.34, units: :distance) # => "34 centimeters" # def number_to_human(number, options = {}) - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) + delegate_number_helper_method(:number_to_human, number, options) + end + + private + + def delegate_number_helper_method(method, number, options) + return unless number + options = escape_unsafe_options(options.symbolize_keys) wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_human(number, options) + ActiveSupport::NumberHelper.public_send(method, number, options) } end - private - - def escape_unsafe_delimiters_and_separators(options) - options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] && !options[:separator].html_safe? - options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] && !options[:delimiter].html_safe? + def escape_unsafe_options(options) + options[:format] = ERB::Util.html_escape(options[:format]) if options[:format] + options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format] + options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] + options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] + options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe? + options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units] options end + def escape_units(units) + Hash[units.map do |k, v| + [k, ERB::Util.html_escape(v)] + end] + end + def wrap_with_output_safety_handling(number, raise_on_invalid, &block) valid_float = valid_float?(number) raise InvalidNumberError, number if raise_on_invalid && !valid_float diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb index 458086de96..ebfc35a4c7 100644 --- a/actionview/lib/action_view/helpers/rendering_helper.rb +++ b/actionview/lib/action_view/helpers/rendering_helper.rb @@ -12,6 +12,14 @@ module ActionView # * <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 + # performs html escape on the string first. Setting the content type as + # <tt>text/html</tt>. + # * <tt>:body</tt> - Renders the text passed in, and inherits the content + # type of <tt>text/html</tt> from <tt>ActionDispatch::Response</tt> + # object. # # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter # as the locals hash. diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb index 732f35643a..a9f3b0ffbc 100644 --- a/actionview/lib/action_view/helpers/tag_helper.rb +++ b/actionview/lib/action_view/helpers/tag_helper.rb @@ -42,7 +42,8 @@ module ActionView # For example, a key +user_id+ would render as <tt>data-user-id</tt> and # thus accessed as <tt>dataset.userId</tt>. # - # Values are encoded to JSON, with the exception of strings and symbols. + # Values are encoded to JSON, with the exception of strings, symbols and + # BigDecimals. # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt> # from 1.4.3. # @@ -56,6 +57,9 @@ module ActionView # tag("input", type: 'text', disabled: true) # # => <input type="text" disabled="disabled" /> # + # tag("input", type: 'text', class: ["strong", "highlight"]) + # # => <input class="strong highlight" type="text" /> + # # tag("img", src: "open & shut.png") # # => <img src="open & shut.png" /> # @@ -75,7 +79,7 @@ module ActionView # Set escape to false to disable attribute value escaping. # # ==== Options - # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and + # The +options+ hash can be used with attributes with no value like (<tt>disabled</tt> and # <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use # symbols or strings for the attribute names. # @@ -84,6 +88,8 @@ module ActionView # # => <p>Hello world!</p> # content_tag(:div, content_tag(:p, "Hello world!"), class: "strong") # # => <div class="strong"><p>Hello world!</p></div> + # content_tag(:div, "Hello world!", class: ["strong", "highlight"]) + # # => <div class="strong highlight">Hello world!</div> # content_tag("select", options, multiple: true) # # => <select multiple="multiple">...options...</select> # @@ -114,7 +120,7 @@ module ActionView # cdata_section("hello]]>world") # # => <![CDATA[hello]]]]><![CDATA[>world]]> def cdata_section(content) - splitted = content.gsub(']]>', ']]]]><![CDATA[>') + splitted = content.to_s.gsub(']]>', ']]]]><![CDATA[>') "<![CDATA[#{splitted}]]>".html_safe end @@ -151,7 +157,7 @@ module ActionView attrs << tag_option(key, value, escape) end end - " #{attrs.sort! * ' '}".html_safe unless attrs.empty? + " #{attrs.sort! * ' '}" unless attrs.empty? end def data_tag_option(key, value, escape) diff --git a/actionview/lib/action_view/helpers/tags.rb b/actionview/lib/action_view/helpers/tags.rb index a05e16979a..45c75d10c0 100644 --- a/actionview/lib/action_view/helpers/tags.rb +++ b/actionview/lib/action_view/helpers/tags.rb @@ -3,37 +3,39 @@ module ActionView module Tags #:nodoc: extend ActiveSupport::Autoload - autoload :Base - autoload :CheckBox - autoload :CollectionCheckBoxes - autoload :CollectionRadioButtons - autoload :CollectionSelect - autoload :ColorField - autoload :DateField - autoload :DateSelect - autoload :DatetimeField - autoload :DatetimeLocalField - autoload :DatetimeSelect - autoload :EmailField - autoload :FileField - autoload :GroupedCollectionSelect - autoload :HiddenField - autoload :Label - autoload :MonthField - autoload :NumberField - autoload :PasswordField - autoload :RadioButton - autoload :RangeField - autoload :SearchField - autoload :Select - autoload :TelField - autoload :TextArea - autoload :TextField - autoload :TimeField - autoload :TimeSelect - autoload :TimeZoneSelect - autoload :UrlField - autoload :WeekField + eager_autoload do + autoload :Base + autoload :CheckBox + autoload :CollectionCheckBoxes + autoload :CollectionRadioButtons + autoload :CollectionSelect + autoload :ColorField + autoload :DateField + autoload :DateSelect + autoload :DatetimeField + autoload :DatetimeLocalField + autoload :DatetimeSelect + autoload :EmailField + autoload :FileField + autoload :GroupedCollectionSelect + autoload :HiddenField + autoload :Label + autoload :MonthField + autoload :NumberField + autoload :PasswordField + autoload :RadioButton + autoload :RangeField + autoload :SearchField + autoload :Select + autoload :TelField + autoload :TextArea + autoload :TextField + autoload :TimeField + autoload :TimeSelect + autoload :TimeZoneSelect + autoload :UrlField + autoload :WeekField + end end end end diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb index 787039c82e..991f32cea2 100644 --- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb +++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb @@ -18,7 +18,7 @@ module ActionView end def label(label_html_options={}, &block) - html_options = label_html_options.merge(@input_html_options) + html_options = @input_html_options.slice(:index, :namespace).merge(label_html_options) @template_object.label(@object_name, @sanitized_attribute_name, @text, html_options, &block) end end diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb index 180aa9ac27..6335e3dd4d 100644 --- a/actionview/lib/action_view/helpers/tags/label.rb +++ b/actionview/lib/action_view/helpers/tags/label.rb @@ -30,14 +30,13 @@ module ActionView add_default_name_and_id_for_value(tag_value, name_and_id) options.delete("index") options.delete("namespace") - options.delete("multiple") options["for"] = name_and_id["id"] unless options.key?("for") if block_given? content = @template_object.capture(&block) else content = if @content.blank? - @object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1') + @object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1') method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name if object.respond_to?(:to_model) diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb index c23d605c5f..7cfbca5b6f 100644 --- a/actionview/lib/action_view/helpers/text_helper.rb +++ b/actionview/lib/action_view/helpers/text_helper.rb @@ -31,6 +31,8 @@ module ActionView include SanitizeHelper include TagHelper + include OutputSafetyHelper + # The preferred method of outputting text in your views is to use the # <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods # do not operate as expected in an eRuby code block. If you absolutely must @@ -80,6 +82,9 @@ module ActionView # # => "And they f... (continued)" # # truncate("<p>Once upon a time in a world far far away</p>") + # # => "<p>Once upon a time in a wo..." + # + # truncate("<p>Once upon a time in a world far far away</p>", escape: false) # # => "<p>Once upon a time in a wo..." # # truncate("Once upon a time in a world far far away") { link_to "Continue", "#" } @@ -268,7 +273,7 @@ module ActionView content_tag(wrapper_tag, nil, html_options) else paragraphs.map! { |paragraph| - content_tag(wrapper_tag, paragraph, html_options, options[:sanitize]) + content_tag(wrapper_tag, raw(paragraph), html_options) }.join("\n\n").html_safe end end @@ -314,7 +319,7 @@ module ActionView options = values.extract_options! name = options.fetch(:name, 'default') - values.unshift(first_value) + values.unshift(*first_value) cycle = get_cycle(name) unless cycle && cycle.values == values diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index ad8eb47f1f..0bc40874d9 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -1,24 +1,14 @@ require 'action_view/helpers/tag_helper' require 'i18n/exceptions' -module I18n - class ExceptionHandler - include Module.new { - def call(exception, locale, key, options) - exception.is_a?(MissingTranslation) && options[:rescue_format] == :html ? super.html_safe : super - end - } - end -end - module ActionView # = Action View Translation Helpers module Helpers module TranslationHelper # Delegates to <tt>I18n#translate</tt> but also performs three additional functions. # - # First, it'll pass the <tt>rescue_format: :html</tt> option to I18n so that any - # thrown +MissingTranslation+ messages will be turned into inline spans that + # First, it will ensure that any thrown +MissingTranslation+ messages will be turned + # into inline spans that: # # * have a "translation-missing" class set, # * contain the missing key as a title attribute and @@ -44,8 +34,17 @@ module ActionView # naming convention helps to identify translations that include HTML tags so that # you know what kind of output to expect when you call translate in a template. def translate(key, options = {}) - options.merge!(:rescue_format => :html) unless options.key?(:rescue_format) options[:default] = wrap_translate_defaults(options[:default]) if options[:default] + + # If the user has specified rescue_format then pass it all through, otherwise use + # raise and do the work ourselves + options[:raise] ||= ActionView::Base.raise_on_missing_translations + + raise_error = options[:raise] || options.key?(:rescue_format) + unless raise_error + options[:raise] = true + end + if html_safe_translation_key?(key) html_safe_options = options.dup options.except(*I18n::RESERVED_KEYS).each do |name, value| @@ -59,6 +58,11 @@ module ActionView else I18n.translate(scope_key_by_partial(key), options) end + rescue I18n::MissingTranslationData => e + raise e if raise_error + + keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope]) + content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}") end alias :t :translate diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 8b78bd32e0..51379d433f 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -82,7 +82,7 @@ module ActionView # to using GET. If <tt>href: '#'</tt> is used and the user has JavaScript # disabled clicking the link will have no effect. If you are relying on the # POST behavior, you should check for it in your controller's action by using - # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>:patch</tt>, or <tt>put?</tt>. + # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>patch?</tt>, or <tt>put?</tt>. # * <tt>remote: true</tt> - This will allow the unobtrusive JavaScript # driver to make an Ajax request to the URL in question instead of following # the link. The drivers each provide mechanisms for listening for the @@ -232,6 +232,11 @@ module ActionView # # <div><input value="New" type="submit" /></div> # # </form>" # + # <%= button_to "New", new_articles_path %> + # # => "<form method="post" action="/articles/new" class="button_to"> + # # <div><input value="New" type="submit" /></div> + # # </form>" + # # <%= button_to [:make_happy, @user] do %> # Make happy <strong><%= @user.name %></strong> # <% end %> @@ -314,8 +319,8 @@ module ActionView inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag) if params - params.each do |name, value| - inner_tags.safe_concat tag(:input, type: "hidden", name: name, value: value.to_param) + params.each do |param_name, value| + inner_tags.safe_concat tag(:input, type: "hidden", name: param_name, value: value.to_param) end end content_tag('form', content_tag('div', inner_tags), form_options) diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb index d8de1d95df..9ee05bd816 100644 --- a/actionview/lib/action_view/layouts.rb +++ b/actionview/lib/action_view/layouts.rb @@ -221,7 +221,7 @@ module ActionView # This module is mixed in if layout conditions are provided. This means # that if no layout conditions are used, this method is not used module LayoutConditions # :nodoc: - private + private # Determines whether the current action has a layout definition by # checking the action name against the :only and :except conditions @@ -269,15 +269,6 @@ module ActionView _write_layout_method end - # If no layout is supplied, look for a template named the return - # value of this method. - # - # ==== Returns - # * <tt>String</tt> - A template name - def _implied_layout_name # :nodoc: - controller_path - end - # Creates a _layout method to be called by _default_layout . # # If a layout is not explicitly mentioned then look for a layout with the controller's name. @@ -335,6 +326,17 @@ module ActionView private :_layout RUBY end + + private + + # If no layout is supplied, look for a template named the return + # value of this method. + # + # ==== Returns + # * <tt>String</tt> - A template name + def _implied_layout_name # :nodoc: + controller_path + end end def _normalize_options(options) # :nodoc: @@ -418,7 +420,7 @@ module ActionView end def _include_layout?(options) - (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout) + (options.keys & [:body, :text, :plain, :html, :inline, :partial]).empty? || options.key?(:layout) end end end diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb index 354a136894..6c8d9cb5bf 100644 --- a/actionview/lib/action_view/log_subscriber.rb +++ b/actionview/lib/action_view/log_subscriber.rb @@ -5,7 +5,12 @@ module ActionView # # Provides functionality so that Rails can output logs from Action View. class LogSubscriber < ActiveSupport::LogSubscriber - VIEWS_PATTERN = /^app\/views\//.freeze + VIEWS_PATTERN = /^app\/views\// + + def initialize + @root = nil + super + end def render_template(event) return unless logger.info? @@ -23,8 +28,15 @@ module ActionView protected + EMPTY = '' def from_rails_root(string) - string.sub("#{Rails.root}/", "").sub(VIEWS_PATTERN, "") + string = string.sub(rails_root, EMPTY) + string.sub!(VIEWS_PATTERN, EMPTY) + string + end + + def rails_root + @root ||= "#{Rails.root}/" end end end diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index f9d5b97fe3..855fed0190 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -1,6 +1,7 @@ require 'thread_safe' require 'active_support/core_ext/module/remove_method' require 'active_support/core_ext/module/attribute_accessors' +require 'action_view/template/resolver' module ActionView # = Action View Lookup Context @@ -52,6 +53,7 @@ module ActionView locales end register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] } + register_detail(:variants) { [] } register_detail(:handlers){ Template::Handlers.extensions } class DetailsKey #:nodoc: @@ -62,6 +64,13 @@ module ActionView @details_keys = ThreadSafe::Cache.new def self.get(details) + if details[:formats] + details = details.dup + syms = Set.new Mime::SET.symbols + details[:formats] = details[:formats].select { |v| + syms.include? v + } + end @details_keys[details] ||= new end @@ -150,7 +159,14 @@ module ActionView def detail_args_for(options) return @details, details_key if options.empty? # most common path. user_details = @details.merge(options) - [user_details, DetailsKey.get(user_details)] + + if @cache + details_key = DetailsKey.get(user_details) + else + details_key = nil + end + + [user_details, details_key] end # Support legacy foo.erb names even though we now ignore .erb diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb index c2783f6377..81f9c40b85 100644 --- a/actionview/lib/action_view/railtie.rb +++ b/actionview/lib/action_view/railtie.rb @@ -36,19 +36,12 @@ module ActionView end end - initializer "action_view.setup_action_pack", before: :add_view_paths do |app| + initializer "action_view.setup_action_pack" do |app| ActiveSupport.on_load(:action_controller) do - ActionController::Base.superclass.send(:include, ActionView::Layouts) ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor) end end - initializer "action_view.setup_action_mailer", before: :add_view_paths do |app| - ActiveSupport.on_load(:action_mailer) do - ActionMailer::Base.send(:include, ActionView::Layouts) - end - end - rake_tasks do load "action_view/tasks/dependencies.rake" end diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index 821026268a..36f17f01fd 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -159,7 +159,7 @@ module ActionView # </div> # # If a collection is given, the layout will be rendered once for each item in - # the collection. Just think these two snippets have the same output: + # the collection. For example, these two snippets have the same output: # # <%# app/views/users/_user.html.erb %> # Name: <%= user.name %> diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb index 9cf6eb0c65..3ab2cd36fc 100644 --- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb @@ -58,7 +58,7 @@ module ActionView def delayed_render(buffer, template, layout, view, locals) # Wrap the given buffer in the StreamingBuffer and pass it to the - # underlying template handler. Now, everytime something is concatenated + # underlying template handler. Now, every time something is concatenated # to the buffer, it is not appended to an array, but streamed straight # to the client. output = ActionView::StreamingBuffer.new(buffer) diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index 668831dff3..be17097428 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -21,8 +21,14 @@ module ActionView def determine_template(options) #:nodoc: keys = options.fetch(:locals, {}).keys - if options.key?(:text) + if options.key?(:body) + Template::Text.new(options[:body]) + elsif options.key?(:text) Template::Text.new(options[:text], formats.first) + elsif options.key?(:plain) + Template::Text.new(options[:plain]) + elsif options.key?(:html) + Template::HTML.new(options[:html], formats.first) elsif options.key?(:file) with_fallbacks { find_template(options[:file], nil, false, keys, @details) } elsif options.key?(:inline) @@ -35,7 +41,7 @@ module ActionView find_template(options[:template], options[:prefixes], false, keys, @details) end else - raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file or :text option." + raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :text or :body option." end end diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index 18a0788f8e..017302d40f 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -77,13 +77,6 @@ module ActionView @_view_renderer ||= ActionView::Renderer.new(lookup_context) end - # Find and renders a template based on the options given. - # :api: private - def _render_template(options) #:nodoc: - lookup_context.rendered_format = nil if options[:formats] - view_renderer.render(view_context, options) - end - def render_to_body(options = {}) _process_options(options) _render_template(options) @@ -95,8 +88,19 @@ module ActionView private + # Find and render a template based on the options given. + # :api: private + def _render_template(options) #:nodoc: + variant = options[:variant] + + lookup_context.rendered_format = nil if options[:formats] + lookup_context.variants = variant if variant + + view_renderer.render(view_context, options) + end + # Assign the rendered format to lookup context. - def _process_format(format) #:nodoc: + def _process_format(format, options = {}) #:nodoc: super lookup_context.formats = [format.to_sym] lookup_context.rendered_format = lookup_context.formats.first diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb index f10e7e88ba..33be06cbf7 100644 --- a/actionview/lib/action_view/routing_url_for.rb +++ b/actionview/lib/action_view/routing_url_for.rb @@ -83,6 +83,8 @@ module ActionView super when :back _back_url + when Array + polymorphic_path(options, options.extract_options!) else polymorphic_path(options) end diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index 9b0619f1aa..9d39d02a37 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -90,13 +90,14 @@ module ActionView eager_autoload do autoload :Error autoload :Handlers + autoload :HTML autoload :Text autoload :Types end extend Template::Handlers - attr_accessor :locals, :formats, :virtual_path + attr_accessor :locals, :formats, :variants, :virtual_path attr_reader :source, :identifier, :handler, :original_encoding, :updated_at @@ -122,6 +123,7 @@ module ActionView @virtual_path = details[:virtual_path] @updated_at = details[:updated_at] || Time.now @formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f } + @variants = [details[:variant]] @compile_mutex = Mutex.new end diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb index a89d51221e..743ef6de0a 100644 --- a/actionview/lib/action_view/template/error.rb +++ b/actionview/lib/action_view/template/error.rb @@ -41,6 +41,9 @@ module ActionView 'template' end + if partial && path.present? + path = path.sub(%r{([^/]+)$}, "_\\1") + end searched_paths = prefixes.map { |prefix| [prefix, path].join("/") } out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n" @@ -56,13 +59,13 @@ module ActionView class Error < ActionViewError #:nodoc: SOURCE_CODE_RADIUS = 3 - attr_reader :original_exception, :backtrace + attr_reader :original_exception def initialize(template, original_exception) super(original_exception.message) @template, @original_exception = template, original_exception @sub_templates = nil - @backtrace = original_exception.backtrace + set_backtrace(original_exception.backtrace) end def file_name diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb index c8a0059596..4523060442 100644 --- a/actionview/lib/action_view/template/handlers/erb.rb +++ b/actionview/lib/action_view/template/handlers/erb.rb @@ -18,7 +18,7 @@ module ActionView src << "@output_buffer.safe_append='" src << "\n" * @newline_pending if @newline_pending > 0 src << escape_text(text) - src << "';" + src << "'.freeze;" @newline_pending = 0 end @@ -67,7 +67,7 @@ module ActionView def flush_newline_if_pending(src) if @newline_pending > 0 - src << "@output_buffer.safe_append='#{"\n" * @newline_pending}';" + src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;" @newline_pending = 0 end end diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb new file mode 100644 index 0000000000..0321f819b5 --- /dev/null +++ b/actionview/lib/action_view/template/html.rb @@ -0,0 +1,34 @@ +module ActionView #:nodoc: + # = Action View HTML Template + class Template + class HTML #:nodoc: + attr_accessor :type + + def initialize(string, type = nil) + @string = string.to_s + @type = Types[type] || type if type + @type ||= Types[:html] + end + + def identifier + 'html template' + end + + def inspect + 'html template' + end + + def to_str + ERB::Util.h(@string) + end + + def render(*args) + to_str + end + + def formats + [@type.respond_to?(:ref) ? @type.ref : @type.to_s] + end + end + end +end diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 3304605c1a..403824bd8e 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -1,6 +1,6 @@ require "pathname" require "active_support/core_ext/class" -require "active_support/core_ext/class/attribute_accessors" +require "active_support/core_ext/module/attribute_accessors" require "action_view/template" require "thread" require "thread_safe" @@ -154,7 +154,8 @@ module ActionView cached = nil templates.each do |t| t.locals = locals - t.formats = details[:formats] || [:html] if t.formats.empty? + t.formats = details[:formats] || [:html] if t.formats.empty? + t.variants = details[:variants] || [] if t.variants.empty? t.virtual_path ||= (cached ||= build_path(*path_info)) end end @@ -162,8 +163,8 @@ module ActionView # An abstract class that implements a Resolver with path semantics. class PathResolver < Resolver #:nodoc: - EXTENSIONS = [:locale, :formats, :handlers] - DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}" + EXTENSIONS = { :locale => ".", :formats => ".", :variants => "+", :handlers => "." } + DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}" def initialize(pattern=nil) @pattern = pattern || DEFAULT_PATTERN @@ -189,13 +190,15 @@ module ActionView } template_paths.map { |template| - handler, format = extract_handler_and_format(template, formats) - contents = File.binread template + handler, format, variant = extract_handler_and_format_and_variant(template, formats) + contents = File.binread(template) Template.new(contents, File.expand_path(template), handler, :virtual_path => path.virtual, :format => format, - :updated_at => mtime(template)) + :variant => variant, + :updated_at => mtime(template) + ) } end @@ -228,7 +231,7 @@ module ActionView # Extract handler and formats from path. If a format cannot be a found neither # from the path, or the handler, we should return the array of formats given # to the resolver. - def extract_handler_and_format(path, default_formats) + def extract_handler_and_format_and_variant(path, default_formats) pieces = File.basename(path).split(".") pieces.shift @@ -240,8 +243,10 @@ module ActionView end handler = Template.handler_for_extension(extension) - format = pieces.last && Template::Types[pieces.last] - [handler, format] + format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last + format &&= Template::Types[format] + + [handler, format, variant] end end @@ -303,12 +308,13 @@ module ActionView # An Optimized resolver for Rails' most common case. class OptimizedFileSystemResolver < FileSystemResolver #:nodoc: def build_query(path, details) - exts = EXTENSIONS.map { |ext| details[ext] } query = escape_entry(File.join(@path, path)) - query + exts.map { |ext| - "{#{ext.compact.uniq.map { |e| ".#{e}," }.join}}" - }.join + exts = EXTENSIONS.map do |ext, prefix| + "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}" + end.join + + query + exts end end diff --git a/actionview/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb index 859c7bc3ce..04f5b8d17a 100644 --- a/actionview/lib/action_view/template/text.rb +++ b/actionview/lib/action_view/template/text.rb @@ -27,7 +27,7 @@ module ActionView #:nodoc: end def formats - [@type.to_sym] + [@type.respond_to?(:ref) ? @type.ref : @type.to_s] end end end diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb index db77cb5d19..b84e0281ae 100644 --- a/actionview/lib/action_view/template/types.rb +++ b/actionview/lib/action_view/template/types.rb @@ -1,5 +1,5 @@ require 'set' -require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/module/attribute_accessors' module ActionView class Template diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb index 7afa2fa613..dfb7d463b4 100644 --- a/actionview/lib/action_view/testing/resolvers.rb +++ b/actionview/lib/action_view/testing/resolvers.rb @@ -21,7 +21,7 @@ module ActionView #:nodoc: def query(path, exts, formats) query = "" - EXTENSIONS.each do |ext| + EXTENSIONS.each_key do |ext| query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)' end query = /^(#{Regexp.escape(path)})#{query}$/ @@ -30,9 +30,13 @@ module ActionView #:nodoc: @hash.each do |_path, array| source, updated_at = array next unless _path =~ query - handler, format = extract_handler_and_format(_path, formats) + handler, format, variant = extract_handler_and_format_and_variant(_path, formats) templates << Template.new(source, _path, handler, - :virtual_path => path.virtual, :format => format, :updated_at => updated_at) + :virtual_path => path.virtual, + :format => format, + :variant => variant, + :updated_at => updated_at + ) end templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size } @@ -41,8 +45,8 @@ module ActionView #:nodoc: class NullResolver < PathResolver def query(path, exts, formats) - handler, format = extract_handler_and_format(path, formats) - [ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format)] + handler, format, variant = extract_handler_and_format_and_variant(path, formats) + [ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format, :variant => variant)] end end diff --git a/actionview/lib/action_view/vendor/html-scanner/html/node.rb b/actionview/lib/action_view/vendor/html-scanner/html/node.rb index 7e7cd4f7b6..27f0f2f6f8 100644 --- a/actionview/lib/action_view/vendor/html-scanner/html/node.rb +++ b/actionview/lib/action_view/vendor/html-scanner/html/node.rb @@ -71,12 +71,12 @@ module HTML #:nodoc: @line, @position = line, pos end - # Return a textual representation of the node. + # Returns a textual representation of the node. def to_s @children.join() end - # Return false (subclasses must override this to provide specific matching + # Returns false (subclasses must override this to provide specific matching # behavior.) +conditions+ may be of any type. def match(conditions) false diff --git a/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb b/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb index 30b6b8b141..ed34eecf55 100644 --- a/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb +++ b/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb @@ -1,6 +1,6 @@ require 'set' require 'cgi' -require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/module/attribute_accessors' module HTML class Sanitizer diff --git a/actionview/lib/action_view/vendor/html-scanner/html/selector.rb b/actionview/lib/action_view/vendor/html-scanner/html/selector.rb index 7f8609c408..dfdd724b9b 100644 --- a/actionview/lib/action_view/vendor/html-scanner/html/selector.rb +++ b/actionview/lib/action_view/vendor/html-scanner/html/selector.rb @@ -488,7 +488,7 @@ module HTML end - # Return the next element after this one. Skips sibling text nodes. + # Returns the next element after this one. Skips sibling text nodes. # # With the +name+ argument, returns the next element with that name, # skipping other sibling elements. diff --git a/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb b/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb index 8ac8d34430..adf4e45930 100644 --- a/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb +++ b/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb @@ -30,7 +30,7 @@ module HTML #:nodoc: @current_line = 1 end - # Return the next token in the sequence, or +nil+ if there are no more tokens in + # Returns the next token in the sequence, or +nil+ if there are no more tokens in # the stream. def next return nil if @scanner.eos? diff --git a/actionview/lib/action_view/version.rb b/actionview/lib/action_view/version.rb index 094dd474df..f55d3fdaef 100644 --- a/actionview/lib/action_view/version.rb +++ b/actionview/lib/action_view/version.rb @@ -1,11 +1,8 @@ +require_relative 'gem_version' + module ActionView - # Returns the version of the currently loaded ActionView as a Gem::Version + # Returns the version of the currently loaded ActionView as a <tt>Gem::Version</tt> def self.version - Gem::Version.new "4.1.0.beta" - end - - module VERSION #:nodoc: - MAJOR, MINOR, TINY, PRE = ActionView.version.segments - STRING = ActionView.version.to_s + gem_version end end diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb index 6623b47e83..9eae3a4fbd 100644 --- a/actionview/test/abstract_unit.rb +++ b/actionview/test/abstract_unit.rb @@ -42,6 +42,9 @@ Thread.abort_on_exception = true # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true +# Disable available locale checks to avoid warnings running the test suite. +I18n.enforce_available_locales = false + # Register danish language for testing I18n.backend.store_translations 'da', {} I18n.backend.store_translations 'pt-BR', {} @@ -267,8 +270,6 @@ class Rack::TestCase < ActionDispatch::IntegrationTest end end -# Emulate AV railtie. -ActionController::Base.superclass.send(:include, ActionView::Layouts) ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor) module ActionController @@ -330,3 +331,11 @@ module ActionDispatch end end +# Skips the current run on Rubinius using Minitest::Assertions#skip +def rubinius_skip(message = '') + skip message if RUBY_ENGINE == 'rbx' +end +# Skips the current run on JRuby using Minitest::Assertions#skip +def jruby_skip(message = '') + skip message if defined?(JRUBY_VERSION) +end diff --git a/actionview/test/actionpack/abstract/helper_test.rb b/actionview/test/actionpack/abstract/helper_test.rb index 4e05245584..7d346e917d 100644 --- a/actionview/test/actionpack/abstract/helper_test.rb +++ b/actionview/test/actionpack/abstract/helper_test.rb @@ -52,7 +52,7 @@ module AbstractController class AbstractInvalidHelpers < AbstractHelpers include ActionController::Helpers - path = File.join(File.expand_path('../../../fixtures', __FILE__), "helpers_missing") + path = File.expand_path('../../../fixtures/helpers_missing', __FILE__) $:.unshift(path) self.helpers_path = path end @@ -78,9 +78,9 @@ module AbstractController end def test_declare_missing_helper - AbstractHelpers.helper :missing - flunk "should have raised an exception" - rescue LoadError => e + e = assert_raise AbstractController::Helpers::MissingHelperError do + AbstractHelpers.helper :missing + end assert_equal "helpers/missing_helper.rb", e.path end diff --git a/actionview/test/actionpack/abstract/layouts_test.rb b/actionview/test/actionpack/abstract/layouts_test.rb index c79cb50fd7..a6786d9b6b 100644 --- a/actionview/test/actionpack/abstract/layouts_test.rb +++ b/actionview/test/actionpack/abstract/layouts_test.rb @@ -214,19 +214,19 @@ module AbstractControllerTests assert_equal "With String Hello string!", controller.response_body end - test "when layout is overwriten by :default in render, render default layout" do + test "when layout is overwritten by :default in render, render default layout" do controller = WithString.new controller.process(:overwrite_default) assert_equal "With String Hello string!", controller.response_body end - test "when layout is overwriten by string in render, render new layout" do + test "when layout is overwritten by string in render, render new layout" do controller = WithString.new controller.process(:overwrite_string) assert_equal "Overwrite Hello string!", controller.response_body end - test "when layout is overwriten by false in render, render no layout" do + test "when layout is overwritten by false in render, render no layout" do controller = WithString.new controller.process(:overwrite_false) assert_equal "Hello string!", controller.response_body @@ -264,7 +264,7 @@ module AbstractControllerTests assert_equal "Overwrite Hello proc!", controller.response_body end - test "when layout is specified as a proc and the proc retuns nil, don't use a layout" do + test "when layout is specified as a proc and the proc returns nil, don't use a layout" do controller = WithProcReturningNil.new controller.process(:index) assert_equal "Hello nil!", controller.response_body diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb index 5dd23c4b31..b44f57a23e 100644 --- a/actionview/test/actionpack/controller/layout_test.rb +++ b/actionview/test/actionpack/controller/layout_test.rb @@ -215,12 +215,6 @@ class LayoutSetInResponseTest < ActionController::TestCase end end -class RenderWithTemplateOptionController < LayoutTest - def hello - render :template => 'alt/hello' - end -end - class SetsNonExistentLayoutFile < LayoutTest layout "nofile" end diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb index 964dccbffb..45b8049b83 100644 --- a/actionview/test/actionpack/controller/render_test.rb +++ b/actionview/test/actionpack/controller/render_test.rb @@ -44,8 +44,6 @@ module Quiz end end - class Store < Question; end - # Controller class QuestionsController < ApplicationController def new @@ -693,9 +691,9 @@ class RenderTest < ActionController::TestCase end def test_line_offset - get :render_line_offset - flunk "the action should have raised an exception" - rescue StandardError => exc + exc = assert_raises ActionView::Template::Error do + get :render_line_offset + end line = exc.backtrace.first assert(line =~ %r{:(\d+):}) assert_equal "1", $1, @@ -971,7 +969,7 @@ class RenderTest < ActionController::TestCase end def test_should_implicitly_render_js_template_without_layout - get :render_implicit_js_template_without_layout, :format => :js + xhr :get, :render_implicit_js_template_without_layout, :format => :js assert_no_match %r{<html>}, @response.body end @@ -992,7 +990,7 @@ class RenderTest < ActionController::TestCase end def test_should_render_formatted_html_erb_template_with_faulty_accepts_header - @request.accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, appliction/x-shockwave-flash, */*" + @request.accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, */*" get :formatted_xml_erb assert_equal '<test>passed formatted html erb</test>', @response.body end diff --git a/actionview/test/activerecord/form_helper_activerecord_test.rb b/actionview/test/activerecord/form_helper_activerecord_test.rb index 2e302c65a7..0a9628da8d 100644 --- a/actionview/test/activerecord/form_helper_activerecord_test.rb +++ b/actionview/test/activerecord/form_helper_activerecord_test.rb @@ -59,7 +59,7 @@ class FormHelperActiveRecordTest < ActionView::TestCase protected def hidden_fields(method = nil) - txt = %{<div style="margin:0;padding:0;display:inline">} + txt = %{<div style="display:none">} txt << %{<input name="utf8" type="hidden" value="✓" />} if method && !%w(get post).include?(method.to_s) txt << %{<input name="_method" type="hidden" value="#{method}" />} diff --git a/actionview/test/fixtures/customers/_customer.xml.erb b/actionview/test/fixtures/customers/_customer.xml.erb new file mode 100644 index 0000000000..d3f1e0768f --- /dev/null +++ b/actionview/test/fixtures/customers/_customer.xml.erb @@ -0,0 +1 @@ +<greeting><%= greeting %></greeting><name><%= customer.name %></name>
\ No newline at end of file diff --git a/actionview/test/fixtures/developer.rb b/actionview/test/fixtures/developer.rb index 4941463015..8b3f0a8039 100644 --- a/actionview/test/fixtures/developer.rb +++ b/actionview/test/fixtures/developer.rb @@ -4,7 +4,3 @@ class Developer < ActiveRecord::Base has_many :topics, :through => :replies accepts_nested_attributes_for :projects end - -class DeVeLoPeR < ActiveRecord::Base - self.table_name = "developers" -end diff --git a/actionview/test/fixtures/digestor/messages/new.html+iphone.erb b/actionview/test/fixtures/digestor/messages/new.html+iphone.erb new file mode 100644 index 0000000000..791e1d36b4 --- /dev/null +++ b/actionview/test/fixtures/digestor/messages/new.html+iphone.erb @@ -0,0 +1,15 @@ +<%# Template Dependency: messages/message %> + +<%= render "header" %> +<%= render "comments/comments" %> + +<%= render "messages/actions/move" %> + +<%= render @message.history.events %> + +<%# render "something_missing" %> +<%# render "something_missing_1" %> + +<% + # Template Dependency: messages/form +%>
\ No newline at end of file diff --git a/actionview/test/fixtures/test/hello_world.html+phone.erb b/actionview/test/fixtures/test/hello_world.html+phone.erb new file mode 100644 index 0000000000..b4f236f878 --- /dev/null +++ b/actionview/test/fixtures/test/hello_world.html+phone.erb @@ -0,0 +1 @@ +Hello phone!
\ No newline at end of file diff --git a/actionview/test/fixtures/test/hello_world.text+phone.erb b/actionview/test/fixtures/test/hello_world.text+phone.erb new file mode 100644 index 0000000000..611e2ee442 --- /dev/null +++ b/actionview/test/fixtures/test/hello_world.text+phone.erb @@ -0,0 +1 @@ +Hello texty phone!
\ No newline at end of file diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 214a13654e..3ca71d3376 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -2,14 +2,6 @@ require 'zlib' require 'abstract_unit' require 'active_support/ordered_options' -class FakeController - attr_accessor :request - - def config - @config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config) - end -end - class AssetTagHelperTest < ActionView::TestCase tests ActionView::Helpers::AssetTagHelper @@ -245,7 +237,7 @@ class AssetTagHelperTest < ActionView::TestCase %(video_tag("gold.m4v", :size => "160x120")) => %(<video height="120" src="/videos/gold.m4v" width="160"></video>), %(video_tag("gold.m4v", "size" => "320x240")) => %(<video height="240" src="/videos/gold.m4v" width="320"></video>), %(video_tag("trailer.ogg", :poster => "screenshot.png")) => %(<video poster="/images/screenshot.png" src="/videos/trailer.ogg"></video>), - %(video_tag("error.avi", "size" => "100")) => %(<video src="/videos/error.avi"></video>), + %(video_tag("error.avi", "size" => "100")) => %(<video height="100" src="/videos/error.avi" width="100"></video>), %(video_tag("error.avi", "size" => "100 x 100")) => %(<video src="/videos/error.avi"></video>), %(video_tag("error.avi", "size" => "x")) => %(<video src="/videos/error.avi"></video>), %(video_tag("http://media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="http://media.rubyonrails.org/video/rails_blog_2.mov"></video>), diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb index 5f09aef249..6f77c3c99d 100644 --- a/actionview/test/template/date_helper_test.rb +++ b/actionview/test/template/date_helper_test.rb @@ -326,6 +326,16 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, select_month(8, :add_month_numbers => true) end + def test_select_month_with_format_string + expected = %(<select id="date_month" name="date[month]">\n) + expected << %(<option value="1">January (01)</option>\n<option value="2">February (02)</option>\n<option value="3">March (03)</option>\n<option value="4">April (04)</option>\n<option value="5">May (05)</option>\n<option value="6">June (06)</option>\n<option value="7">July (07)</option>\n<option value="8" selected="selected">August (08)</option>\n<option value="9">September (09)</option>\n<option value="10">October (10)</option>\n<option value="11">November (11)</option>\n<option value="12">December (12)</option>\n) + expected << "</select>\n" + + format_string = '%{name} (%<number>02d)' + assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), :month_format_string => format_string) + assert_dom_equal expected, select_month(8, :month_format_string => format_string) + end + def test_select_month_with_numbers_and_names_with_abbv expected = %(<select id="date_month" name="date[month]">\n) expected << %(<option value="1">1 - Jan</option>\n<option value="2">2 - Feb</option>\n<option value="3">3 - Mar</option>\n<option value="4">4 - Apr</option>\n<option value="5">5 - May</option>\n<option value="6">6 - Jun</option>\n<option value="7">7 - Jul</option>\n<option value="8" selected="selected">8 - Aug</option>\n<option value="9">9 - Sep</option>\n<option value="10">10 - Oct</option>\n<option value="11">11 - Nov</option>\n<option value="12">12 - Dec</option>\n) diff --git a/actionview/test/template/dependency_tracker_test.rb b/actionview/test/template/dependency_tracker_test.rb index 7a9b4b26ac..df3a0602d1 100644 --- a/actionview/test/template/dependency_tracker_test.rb +++ b/actionview/test/template/dependency_tracker_test.rb @@ -1,3 +1,5 @@ +# encoding: utf-8 + require 'abstract_unit' require 'action_view/dependency_tracker' @@ -52,23 +54,127 @@ class ERBTrackerTest < Minitest::Test def test_dependency_of_erb_template_with_number_in_filename template = FakeTemplate.new("<%# render 'messages/message123' %>", :erb) - tracker = make_tracker('messages/_message123', template) + tracker = make_tracker("messages/_message123", template) assert_equal ["messages/message123"], tracker.dependencies end def test_finds_dependency_in_correct_directory template = FakeTemplate.new("<%# render(message.topic) %>", :erb) - tracker = make_tracker('messages/_message', template) + tracker = make_tracker("messages/_message", template) assert_equal ["topics/topic"], tracker.dependencies end def test_finds_dependency_in_correct_directory_with_underscore template = FakeTemplate.new("<%# render(message_type.messages) %>", :erb) - tracker = make_tracker('message_types/_message_type', template) + tracker = make_tracker("message_types/_message_type", template) assert_equal ["messages/message"], tracker.dependencies end -end + def test_dependency_of_erb_template_with_no_spaces_after_render + template = FakeTemplate.new("<%# render'messages/message' %>", :erb) + tracker = make_tracker("messages/_message", template) + + assert_equal ["messages/message"], tracker.dependencies + end + + def test_finds_no_dependency_when_render_begins_the_name_of_an_identifier + template = FakeTemplate.new("<%# rendering 'it useless' %>", :erb) + tracker = make_tracker("resources/_resource", template) + + assert_equal [], tracker.dependencies + end + + def test_finds_no_dependency_when_render_ends_the_name_of_another_method + template = FakeTemplate.new("<%# surrender 'to reason' %>", :erb) + tracker = make_tracker("resources/_resource", template) + + assert_equal [], tracker.dependencies + end + + def test_finds_dependency_on_multiline_render_calls + template = FakeTemplate.new("<%# + render :object => @all_posts, + :partial => 'posts' %>", :erb) + + tracker = make_tracker("some/_little_posts", template) + + assert_equal ["some/posts"], tracker.dependencies + end + + def test_finds_multiple_unrelated_odd_dependencies + template = FakeTemplate.new(" + <%# render('shared/header', title: 'Title') %> + <h2>Section title</h2> + <%# render@section %> + ", :erb) + + tracker = make_tracker("multiple/_dependencies", template) + + assert_equal ["shared/header", "sections/section"], tracker.dependencies + end + + def test_finds_dependencies_for_all_kinds_of_identifiers + template = FakeTemplate.new(" + <%# render $globals %> + <%# render @instance_variables %> + <%# render @@class_variables %> + ", :erb) + + tracker = make_tracker("identifiers/_all", template) + + assert_equal [ + "globals/global", + "instance_variables/instance_variable", + "class_variables/class_variable" + ], tracker.dependencies + end + + def test_finds_dependencies_on_method_chains + template = FakeTemplate.new("<%# render @parent.child.grandchildren %>", :erb) + tracker = make_tracker("method/_chains", template) + + assert_equal ["grandchildren/grandchild"], tracker.dependencies + end + + def test_finds_dependencies_with_special_characters + template = FakeTemplate.new("<%# render @pokémon, partial: 'ピカチュウ' %>", :erb) + tracker = make_tracker("special/_characters", template) + + assert_equal ["special/ピカチュウ"], tracker.dependencies + end + + def test_finds_dependencies_with_quotes_within + template = FakeTemplate.new(%{ + <%# render "single/quote's" %> + <%# render 'double/quote"s' %> + }, :erb) + + tracker = make_tracker("quotes/_single_and_double", template) + + assert_equal ["single/quote's", 'double/quote"s'], tracker.dependencies + end + + def test_finds_dependencies_with_extra_spaces + template = FakeTemplate.new(%{ + <%= render "header" %> + <%= render partial: "form" %> + <%= render @message %> + <%= render ( @message.events ) %> + <%= render :collection => @message.comments, + :partial => "comments/comment" %> + }, :erb) + + tracker = make_tracker("spaces/_extra", template) + + assert_equal [ + "spaces/header", + "spaces/form", + "messages/message", + "events/event", + "comments/comment" + ], tracker.dependencies + end +end diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb index 779a7fb53c..0cbfd14c94 100644 --- a/actionview/test/template/digestor_test.rb +++ b/actionview/test/template/digestor_test.rb @@ -15,18 +15,30 @@ end class FixtureFinder FIXTURES_DIR = "#{File.dirname(__FILE__)}/../fixtures/digestor" - attr_reader :details + attr_reader :details + attr_accessor :formats + attr_accessor :variants def initialize - @details = {} + @details = {} + @formats = [] + @variants = [] end def details_key details.hash end - def find(logical_name, keys, partial, options) - FixtureTemplate.new("digestor/#{partial ? logical_name.gsub(%r|/([^/]+)$|, '/_\1') : logical_name}.#{options[:formats].first}.erb") + def find(name, prefixes = [], partial = false, keys = [], options = {}) + partial_name = partial ? name.gsub(%r|/([^/]+)$|, '/_\1') : name + format = @formats.first.to_s + format += "+#{@variants.first}" if @variants.any? + + FixtureTemplate.new("digestor/#{partial_name}.#{format}.erb") + end + + def disable_cache(&block) + yield end end @@ -194,6 +206,13 @@ class TemplateDigestorTest < ActionView::TestCase end end + def test_variants + assert_digest_difference("messages/new", false, variant: :iphone) do + change_template("messages/new", :iphone) + change_template("messages/_header", :iphone) + end + end + def test_dependencies_via_options_results_in_different_digest digest_plain = digest("comments/_comment") digest_fridge = digest("comments/_comment", dependencies: ["fridge"]) @@ -242,6 +261,11 @@ class TemplateDigestorTest < ActionView::TestCase ActionView::Resolver.caching = resolver_before end + def test_arguments_deprecation + assert_deprecated(/will be removed/) { ActionView::Digestor.digest('messages/show', :html, finder) } + assert_deprecated(/will be removed/) { ActionView::Digestor.new('messages/show', :html, finder) } + end + private def assert_logged(message) old_logger = ActionView::Base.logger @@ -258,26 +282,32 @@ class TemplateDigestorTest < ActionView::TestCase end end - def assert_digest_difference(template_name, persistent = false) - previous_digest = digest(template_name) + def assert_digest_difference(template_name, persistent = false, options = {}) + previous_digest = digest(template_name, options) ActionView::Digestor.cache.clear unless persistent yield - assert previous_digest != digest(template_name), "digest didn't change" + assert previous_digest != digest(template_name, options), "digest didn't change" ActionView::Digestor.cache.clear end - def digest(template_name, options={}) - ActionView::Digestor.digest(template_name, :html, finder, options) + def digest(template_name, options = {}) + options = options.dup + finder.formats = [:html] + finder.variants = [options[:variant]] if options[:variant].present? + + ActionView::Digestor.digest({ name: template_name, format: :html, finder: finder }.merge(options)) end def finder @finder ||= FixtureFinder.new end - def change_template(template_name) - File.open("digestor/#{template_name}.html.erb", "w") do |f| + def change_template(template_name, variant = nil) + variant = "+#{variant}" if variant.present? + + File.open("digestor/#{template_name}.html#{variant}.erb", "w") do |f| f.write "\nTHIS WAS CHANGED!" end end diff --git a/actionview/test/template/erb_util_test.rb b/actionview/test/template/erb_util_test.rb index 3e5b029cea..9bacbba908 100644 --- a/actionview/test/template/erb_util_test.rb +++ b/actionview/test/template/erb_util_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'active_support/json' class ErbUtilTest < ActiveSupport::TestCase include ERB::Util @@ -15,6 +16,51 @@ class ErbUtilTest < ActiveSupport::TestCase end end + HTML_ESCAPE_TEST_CASES = [ + ['<br>', '<br>'], + ['a & b', 'a & b'], + ['"quoted" string', '"quoted" string'], + ["'quoted' string", ''quoted' string'], + [ + '<script type="application/javascript">alert("You are \'pwned\'!")</script>', + '<script type="application/javascript">alert("You are 'pwned'!")</script>' + ] + ] + + JSON_ESCAPE_TEST_CASES = [ + ['1', '1'], + ['null', 'null'], + ['"&"', '"\u0026"'], + ['"</script>"', '"\u003c/script\u003e"'], + ['["</script>"]', '["\u003c/script\u003e"]'], + ['{"name":"</script>"}', '{"name":"\u003c/script\u003e"}'], + [%({"name":"d\u2028h\u2029h"}), '{"name":"d\u2028h\u2029h"}'] + ] + + def test_html_escape + HTML_ESCAPE_TEST_CASES.each do |(raw, expected)| + assert_equal expected, html_escape(raw) + end + end + + def test_json_escape + JSON_ESCAPE_TEST_CASES.each do |(raw, expected)| + assert_equal expected, json_escape(raw) + end + end + + def test_json_escape_does_not_alter_json_string_meaning + JSON_ESCAPE_TEST_CASES.each do |(raw, _)| + assert_equal ActiveSupport::JSON.decode(raw), ActiveSupport::JSON.decode(json_escape(raw)) + end + end + + def test_json_escape_is_idempotent + JSON_ESCAPE_TEST_CASES.each do |(raw, _)| + assert_equal json_escape(raw), json_escape(json_escape(raw)) + end + end + def test_json_escape_returns_unsafe_strings_when_passed_unsafe_strings value = json_escape("asdf") assert !value.html_safe? @@ -31,7 +77,7 @@ class ErbUtilTest < ActiveSupport::TestCase assert escaped.html_safe? end - def test_html_escape_passes_html_escpe_unmodified + def test_html_escape_passes_html_escape_unmodified escaped = h("<p>".html_safe) assert_equal "<p>", escaped assert escaped.html_safe? diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb index 4e9cd74df5..73fa3b6b4e 100644 --- a/actionview/test/template/form_collections_helper_test.rb +++ b/actionview/test/template/form_collections_helper_test.rb @@ -60,7 +60,7 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_no_select 'input[type=radio][value=other][disabled=disabled]' end - test 'collection radio accepts single disable item' do + test 'collection radio accepts single disabled item' do collection = [[1, true], [0, false]] with_collection_radio_buttons :user, :active, collection, :last, :first, :disabled => true @@ -84,6 +84,24 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select 'input[type=radio][value=false].bar#user_active_false' end + test 'collection radio sets the label class defined inside the block' do + collection = [[1, true, {class: 'foo'}], [0, false, {class: 'bar'}]] + with_collection_radio_buttons :user, :active, collection, :second, :first do |b| + b.label(class: "collection_radio_buttons") + end + + assert_select 'label.collection_radio_buttons[for=user_active_true]' + assert_select 'label.collection_radio_buttons[for=user_active_false]' + end + + test 'collection radio does not include the input class in the respective label' do + collection = [[1, true, {class: 'foo'}], [0, false, {class: 'bar'}]] + with_collection_radio_buttons :user, :active, collection, :second, :first + + assert_no_select 'label.foo[for=user_active_true]' + assert_no_select 'label.bar[for=user_active_false]' + end + test 'collection radio does not wrap input inside the label' do with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s @@ -164,7 +182,7 @@ class FormCollectionsHelperTest < ActionView::TestCase end # COLLECTION CHECK BOXES - test 'collection check boxes accepts a collection and generate a serie of checkboxes for value method' do + test 'collection check boxes accepts a collection and generate a series of checkboxes for value method' do collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] with_collection_check_boxes :user, :category_ids, collection, :id, :name @@ -193,7 +211,7 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select "input[type=hidden][name='user[category_ids][]'][value=]", :count => 0 end - test 'collection check boxes accepts a collection and generate a serie of checkboxes with labels for label method' do + test 'collection check boxes accepts a collection and generate a series of checkboxes with labels for label method' do collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] with_collection_check_boxes :user, :category_ids, collection, :id, :name @@ -222,6 +240,24 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select 'input[type=checkbox][value=2].bar' end + test 'collection check boxes sets the label class defined inside the block' do + collection = [[1, 'Category 1', {class: 'foo'}], [2, 'Category 2', {class: 'bar'}]] + with_collection_check_boxes :user, :active, collection, :second, :first do |b| + b.label(class: 'collection_check_boxes') + end + + assert_select 'label.collection_check_boxes[for=user_active_category_1]' + assert_select 'label.collection_check_boxes[for=user_active_category_2]' + end + + test 'collection check boxes does not include the input class in the respective label' do + collection = [[1, 'Category 1', {class: 'foo'}], [2, 'Category 2', {class: 'bar'}]] + with_collection_check_boxes :user, :active, collection, :second, :first + + assert_no_select 'label.foo[for=user_active_category_1]' + assert_no_select 'label.bar[for=user_active_category_2]' + end + test 'collection check boxes accepts selected values as :checked option' do collection = (1..3).map{|i| [i, "Category #{i}"] } with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => [1, 3] @@ -271,7 +307,7 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_no_select 'input[type=checkbox][value=2][disabled=disabled]' end - test 'collection check boxes accepts single disable item' do + test 'collection check boxes accepts single disabled item' do collection = (1..3).map{|i| [i, "Category #{i}"] } with_collection_check_boxes :user, :category_ids, collection, :first, :last, :disabled => 1 diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index 3e8a2468ed..b5e9801776 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -143,75 +143,68 @@ class FormHelperTest < ActionView::TestCase end def test_label_with_locales_strings - old_locale, I18n.locale = I18n.locale, :label - assert_dom_equal('<label for="post_body">Write entire text here</label>', label("post", "body")) - ensure - I18n.locale = old_locale + with_locale :label do + assert_dom_equal('<label for="post_body">Write entire text here</label>', label("post", "body")) + end end def test_label_with_human_attribute_name - old_locale, I18n.locale = I18n.locale, :label - assert_dom_equal('<label for="post_cost">Total cost</label>', label(:post, :cost)) - ensure - I18n.locale = old_locale + with_locale :label do + assert_dom_equal('<label for="post_cost">Total cost</label>', label(:post, :cost)) + end end def test_label_with_locales_symbols - old_locale, I18n.locale = I18n.locale, :label - assert_dom_equal('<label for="post_body">Write entire text here</label>', label(:post, :body)) - ensure - I18n.locale = old_locale + with_locale :label do + assert_dom_equal('<label for="post_body">Write entire text here</label>', label(:post, :body)) + end end def test_label_with_locales_and_options - old_locale, I18n.locale = I18n.locale, :label - assert_dom_equal( - '<label for="post_body" class="post_body">Write entire text here</label>', - label(:post, :body, class: "post_body") - ) - ensure - I18n.locale = old_locale + with_locale :label do + assert_dom_equal( + '<label for="post_body" class="post_body">Write entire text here</label>', + label(:post, :body, class: "post_body") + ) + end end def test_label_with_locales_and_value - old_locale, I18n.locale = I18n.locale, :label - assert_dom_equal('<label for="post_color_red">Rojo</label>', label(:post, :color, value: "red")) - ensure - I18n.locale = old_locale + with_locale :label do + assert_dom_equal('<label for="post_color_red">Rojo</label>', label(:post, :color, value: "red")) + end end def test_label_with_locales_and_nested_attributes - old_locale, I18n.locale = I18n.locale, :label - form_for(@post, html: { id: 'create-post' }) do |f| - f.fields_for(:comments) do |cf| - concat cf.label(:body) + with_locale :label do + form_for(@post, html: { id: 'create-post' }) do |f| + f.fields_for(:comments) do |cf| + concat cf.label(:body) + end end - end - expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do - '<label for="post_comments_attributes_0_body">Write body here</label>' - end + expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do + '<label for="post_comments_attributes_0_body">Write body here</label>' + end - assert_dom_equal expected, output_buffer - ensure - I18n.locale = old_locale + assert_dom_equal expected, output_buffer + end end def test_label_with_locales_fallback_and_nested_attributes - old_locale, I18n.locale = I18n.locale, :label - form_for(@post, html: { id: 'create-post' }) do |f| - f.fields_for(:tags) do |cf| - concat cf.label(:value) + with_locale :label do + form_for(@post, html: { id: 'create-post' }) do |f| + f.fields_for(:tags) do |cf| + concat cf.label(:value) + end end - end - expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do - '<label for="post_tags_attributes_0_value">Tag</label>' - end + expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do + '<label for="post_tags_attributes_0_value">Tag</label>' + end - assert_dom_equal expected, output_buffer - ensure - I18n.locale = old_locale + assert_dom_equal expected, output_buffer + end end def test_label_with_for_attribute_as_symbol @@ -1300,6 +1293,24 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_with_index_and_with_collection_radio_buttons + post = Post.new + def post.active; false; end + + form_for(post, index: '1') do |f| + concat f.collection_radio_buttons(:active, [true, false], :to_s, :to_s) + end + + expected = whole_form("/posts", "new_post", "new_post") do + "<input id='post_1_active_true' name='post[1][active]' type='radio' value='true' />" + + "<label for='post_1_active_true'>true</label>" + + "<input checked='checked' id='post_1_active_false' name='post[1][active]' type='radio' value='false' />" + + "<label for='post_1_active_false'>false</label>" + end + + assert_dom_equal expected, output_buffer + end + def test_form_for_with_collection_check_boxes post = Post.new def post.tag_ids; [1, 3]; end @@ -1397,6 +1408,24 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_with_index_and_with_collection_check_boxes + post = Post.new + def post.tag_ids; [1]; end + collection = [[1, "Tag 1"]] + + form_for(post, index: '1') do |f| + concat f.collection_check_boxes(:tag_ids, collection, :first, :last) + end + + expected = whole_form("/posts", "new_post", "new_post") do + "<input checked='checked' id='post_1_tag_ids_1' name='post[1][tag_ids][]' type='checkbox' value='1' />" + + "<label for='post_1_tag_ids_1'>Tag 1</label>" + + "<input name='post[tag_ids][]' type='hidden' value='' />" + end + + assert_dom_equal expected, output_buffer + end + def test_form_for_with_file_field_generate_multipart Post.send :attr_accessor, :file @@ -1775,69 +1804,61 @@ class FormHelperTest < ActionView::TestCase end def test_submit_with_object_as_new_record_and_locale_strings - old_locale, I18n.locale = I18n.locale, :submit + with_locale :submit do + @post.persisted = false + @post.stubs(:to_key).returns(nil) + form_for(@post) do |f| + concat f.submit + end - @post.persisted = false - @post.stubs(:to_key).returns(nil) - form_for(@post) do |f| - concat f.submit - end + expected = whole_form('/posts', 'new_post', 'new_post') do + "<input name='commit' type='submit' value='Create Post' />" + end - expected = whole_form('/posts', 'new_post', 'new_post') do - "<input name='commit' type='submit' value='Create Post' />" + assert_dom_equal expected, output_buffer end - - assert_dom_equal expected, output_buffer - ensure - I18n.locale = old_locale end def test_submit_with_object_as_existing_record_and_locale_strings - old_locale, I18n.locale = I18n.locale, :submit + with_locale :submit do + form_for(@post) do |f| + concat f.submit + end - form_for(@post) do |f| - concat f.submit - end + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do + "<input name='commit' type='submit' value='Confirm Post changes' />" + end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do - "<input name='commit' type='submit' value='Confirm Post changes' />" + assert_dom_equal expected, output_buffer end - - assert_dom_equal expected, output_buffer - ensure - I18n.locale = old_locale end def test_submit_without_object_and_locale_strings - old_locale, I18n.locale = I18n.locale, :submit + with_locale :submit do + form_for(:post) do |f| + concat f.submit class: "extra" + end - form_for(:post) do |f| - concat f.submit class: "extra" - end + expected = whole_form do + "<input name='commit' class='extra' type='submit' value='Save changes' />" + end - expected = whole_form do - "<input name='commit' class='extra' type='submit' value='Save changes' />" + assert_dom_equal expected, output_buffer end - - assert_dom_equal expected, output_buffer - ensure - I18n.locale = old_locale end def test_submit_with_object_and_nested_lookup - old_locale, I18n.locale = I18n.locale, :submit + with_locale :submit do + form_for(@post, as: :another_post) do |f| + concat f.submit + end - form_for(@post, as: :another_post) do |f| - concat f.submit - end + expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', method: 'patch') do + "<input name='commit' type='submit' value='Update your Post' />" + end - expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', method: 'patch') do - "<input name='commit' type='submit' value='Update your Post' />" + assert_dom_equal expected, output_buffer end - - assert_dom_equal expected, output_buffer - ensure - I18n.locale = old_locale end def test_nested_fields_for @@ -2369,6 +2390,18 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_nested_fields_label_translation_with_more_than_10_records + @post.comments = Array.new(11) { |id| Comment.new(id + 1) } + + I18n.expects(:t).with('post.comments.body', default: [:"comment.body", ''], scope: "helpers.label").times(11).returns "Write body here" + + form_for(@post) do |f| + f.fields_for(:comments) do |cf| + concat cf.label(:body) + end + end + end + def test_nested_fields_for_with_existing_records_on_a_supplied_nested_attributes_collection_different_from_record_one comments = Array.new(2) { |id| Comment.new(id + 1) } @post.comments = [] @@ -2987,7 +3020,7 @@ class FormHelperTest < ActionView::TestCase protected def hidden_fields(method = nil) - txt = %{<div style="margin:0;padding:0;display:inline">} + txt = %{<div style="display:none">} txt << %{<input name="utf8" type="hidden" value="✓" />} if method && !%w(get post).include?(method.to_s) txt << %{<input name="_method" type="hidden" value="#{method}" />} @@ -3016,4 +3049,11 @@ class FormHelperTest < ActionView::TestCase def protect_against_forgery? false end + + def with_locale(testing_locale = :label) + old_locale, I18n.locale = I18n.locale, testing_locale + yield + ensure + I18n.locale = old_locale + end end diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb index 22bf438a56..cf824e2733 100644 --- a/actionview/test/template/form_tag_helper_test.rb +++ b/actionview/test/template/form_tag_helper_test.rb @@ -14,7 +14,7 @@ class FormTagHelperTest < ActionView::TestCase method = options[:method] enforce_utf8 = options.fetch(:enforce_utf8, true) - txt = %{<div style="margin:0;padding:0;display:inline">} + txt = %{<div style="display:none">} txt << %{<input name="utf8" type="hidden" value="✓" />} if enforce_utf8 if method && !%w(get post).include?(method.to_s) txt << %{<input name="_method" type="hidden" value="#{method}" />} @@ -476,6 +476,11 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal('<button name="temptation" type="button"><strong>Do not press me</strong></button>', output) end + def test_button_tag_defaults_with_block_and_options + output = button_tag(:name => 'temptation', :value => 'within') { content_tag(:strong, 'Do not press me') } + assert_dom_equal('<button name="temptation" value="within" type="submit" ><strong>Do not press me</strong></button>', output) + end + def test_button_tag_with_confirmation assert_dom_equal( %(<button name="button" type="submit" data-confirm="Are you sure?">Save</button>), diff --git a/actionview/test/template/html_test.rb b/actionview/test/template/html_test.rb new file mode 100644 index 0000000000..549c12c88c --- /dev/null +++ b/actionview/test/template/html_test.rb @@ -0,0 +1,17 @@ +require 'abstract_unit' + +class HTMLTest < ActiveSupport::TestCase + test 'formats returns symbol for recognized MIME type' do + assert_equal [:html], ActionView::Template::HTML.new('', :html).formats + end + + test 'formats returns string for recognized MIME type when MIME does not have symbol' do + foo = Mime::Type.lookup("foo") + assert_nil foo.to_sym + assert_equal ['foo'], ActionView::Template::HTML.new('', foo).formats + end + + test 'formats returns string for unknown MIME type' do + assert_equal ['foo'], ActionView::Template::HTML.new('', 'foo').formats + end +end diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb index 203ad6d910..4f7823045e 100644 --- a/actionview/test/template/lookup_context_test.rb +++ b/actionview/test/template/lookup_context_test.rb @@ -36,6 +36,11 @@ class LookupContextTest < ActiveSupport::TestCase assert @lookup_context.formats.frozen? end + test "provides getters and setters for variants" do + @lookup_context.variants = [:mobile] + assert_equal [:mobile], @lookup_context.variants + end + test "provides getters and setters for formats" do @lookup_context.formats = [:html] assert_equal [:html], @lookup_context.formats @@ -88,6 +93,20 @@ class LookupContextTest < ActiveSupport::TestCase assert_equal "Hey verden", template.source end + test "find templates with given variants" do + @lookup_context.formats = [:html] + @lookup_context.variants = [:phone] + + template = @lookup_context.find("hello_world", %w(test)) + assert_equal "Hello phone!", template.source + + @lookup_context.variants = [:phone] + @lookup_context.formats = [:text] + + template = @lookup_context.find("hello_world", %w(test)) + assert_equal "Hello texty phone!", template.source + end + test "found templates respects given formats if one cannot be found from template or handler" do ActionView::Template::Handlers::Builder.expects(:default_format).returns(nil) @lookup_context.formats = [:text] @@ -249,15 +268,15 @@ class TestMissingTemplate < ActiveSupport::TestCase e = assert_raise ActionView::MissingTemplate do @lookup_context.find("foo", %w(parent child), true) end - assert_match %r{Missing partial parent/foo, child/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message + assert_match %r{Missing partial parent/_foo, child/_foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message end test "if a single prefix is passed as a string and the lookup fails, MissingTemplate accepts it" do e = assert_raise ActionView::MissingTemplate do - details = {:handlers=>[], :formats=>[], :locale=>[]} + details = {:handlers=>[], :formats=>[], :variants=>[], :locale=>[]} @lookup_context.view_paths.find("foo", "parent", true, details) end - assert_match %r{Missing partial parent/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message + assert_match %r{Missing partial parent/_foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message end end diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb index 6e640889d2..11bc978324 100644 --- a/actionview/test/template/number_helper_test.rb +++ b/actionview/test/template/number_helper_test.rb @@ -8,18 +8,27 @@ class NumberHelperTest < ActionView::TestCase assert_equal "555-1234", number_to_phone(5551234) assert_equal "(800) 555-1212 x 123", number_to_phone(8005551212, area_code: true, extension: 123) assert_equal "+18005551212", number_to_phone(8005551212, country_code: 1, delimiter: "") + assert_equal "+<script></script>8005551212", number_to_phone(8005551212, country_code: "<script></script>", delimiter: "") + assert_equal "8005551212 x <script></script>", number_to_phone(8005551212, extension: "<script></script>", delimiter: "") end def test_number_to_currency assert_equal nil, number_to_currency(nil) assert_equal "$1,234,567,890.50", number_to_currency(1234567890.50) assert_equal "$1,234,567,892", number_to_currency(1234567891.50, precision: 0) - assert_equal "1,234,567,890.50 - Kč", number_to_currency("-1234567890.50", unit: "Kč", format: "%n %u", negative_format: "%n - %u") + assert_equal "1,234,567,890.50 - Kč", number_to_currency("-1234567890.50", unit: raw("Kč"), format: "%n %u", negative_format: "%n - %u") + assert_equal "&pound;1,234,567,890.50", number_to_currency("1234567890.50", unit: "£") + assert_equal "<b>1,234,567,890.50</b> $", number_to_currency("1234567890.50", format: "<b>%n</b> %u") + assert_equal "<b>1,234,567,890.50</b> $", number_to_currency("-1234567890.50", negative_format: "<b>%n</b> %u") + assert_equal "<b>1,234,567,890.50</b> $", number_to_currency("-1234567890.50", 'negative_format' => "<b>%n</b> %u") end def test_number_to_percentage assert_equal nil, number_to_percentage(nil) assert_equal "100.000%", number_to_percentage(100) + assert_equal "100.000 %", number_to_percentage(100, format: '%n %') + assert_equal "<b>100.000</b> %", number_to_percentage(100, format: '<b>%n</b> %') + assert_equal "<b>100.000</b> %", number_to_percentage(100, format: raw('<b>%n</b> %')) assert_equal "100%", number_to_percentage(100, precision: 0) assert_equal "123.4%", number_to_percentage(123.400, precision: 3, strip_insignificant_zeros: true) assert_equal "1.000,000%", number_to_percentage(1000, delimiter: ".", separator: ",") @@ -51,6 +60,31 @@ class NumberHelperTest < ActionView::TestCase assert_equal "489.0 Thousand", number_to_human(489000, precision: 4, strip_insignificant_zeros: false) end + def test_number_to_human_escape_units + volume = { unit: "<b>ml</b>", thousand: "<b>lt</b>", million: "<b>m3</b>", trillion: "<b>km3</b>", quadrillion: "<b>Pl</b>" } + assert_equal '123 <b>lt</b>', number_to_human(123456, :units => volume) + assert_equal '12 <b>ml</b>', number_to_human(12, :units => volume) + assert_equal '1.23 <b>m3</b>', number_to_human(1234567, :units => volume) + assert_equal '1.23 <b>km3</b>', number_to_human(1_234_567_000_000, :units => volume) + assert_equal '1.23 <b>Pl</b>', number_to_human(1_234_567_000_000_000, :units => volume) + + #Including fractionals + distance = { mili: "<b>mm</b>", centi: "<b>cm</b>", deci: "<b>dm</b>", unit: "<b>m</b>", + ten: "<b>dam</b>", hundred: "<b>hm</b>", thousand: "<b>km</b>", + micro: "<b>um</b>", nano: "<b>nm</b>", pico: "<b>pm</b>", femto: "<b>fm</b>"} + assert_equal '1.23 <b>mm</b>', number_to_human(0.00123, :units => distance) + assert_equal '1.23 <b>cm</b>', number_to_human(0.0123, :units => distance) + assert_equal '1.23 <b>dm</b>', number_to_human(0.123, :units => distance) + assert_equal '1.23 <b>m</b>', number_to_human(1.23, :units => distance) + assert_equal '1.23 <b>dam</b>', number_to_human(12.3, :units => distance) + assert_equal '1.23 <b>hm</b>', number_to_human(123, :units => distance) + assert_equal '1.23 <b>km</b>', number_to_human(1230, :units => distance) + assert_equal '1.23 <b>um</b>', number_to_human(0.00000123, :units => distance) + assert_equal '1.23 <b>nm</b>', number_to_human(0.00000000123, :units => distance) + assert_equal '1.23 <b>pm</b>', number_to_human(0.00000000000123, :units => distance) + assert_equal '1.23 <b>fm</b>', number_to_human(0.00000000000000123, :units => distance) + end + def test_number_helpers_escape_delimiter_and_separator assert_equal "111<script></script>111<script></script>1111", number_to_phone(1111111111, delimiter: "<script></script>") @@ -72,6 +106,12 @@ class NumberHelperTest < ActionView::TestCase assert_equal "100<script></script>000 Quadrillion", number_to_human(10**20, delimiter: "<script></script>") end + def test_number_to_human_with_custom_translation_scope + I18n.backend.store_translations 'ts', + :custom_units_for_number_to_human => {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"} + assert_equal "1.01 cm", number_to_human(0.0101, :locale => 'ts', :units => :custom_units_for_number_to_human) + end + def test_number_helpers_outputs_are_html_safe assert number_to_human(1).html_safe? assert !number_to_human("<script></script>").html_safe? diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index 928dfb092d..ca508abfb8 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -22,7 +22,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 :partial, :template, :inline, :file or :text option.", e.message + assert_match(/You invoked render but did not give any of (.+) option./, e.message) end def test_render_file @@ -63,7 +63,7 @@ module RenderTestCases def test_render_template_with_a_missing_partial_of_another_format @view.lookup_context.formats = [:html] - assert_raise ActionView::Template::Error, "Missing partial /missing with {:locale=>[:en], :formats=>[:json], :handlers=>[:erb, :builder]}" do + assert_raise ActionView::Template::Error, "Missing partial /_missing with {:locale=>[:en], :formats=>[:json], :handlers=>[:erb, :builder]}" do @view.render(:template => "with_format", :formats => [:json]) end end @@ -304,6 +304,16 @@ module RenderTestCases assert_equal "Hola: david", @controller_view.render('customer_greeting', :greeting => 'Hola', :customer_greeting => Customer.new("david")) end + def test_render_partial_with_object_uses_render_partial_path + assert_equal "Hello: lifo", + @controller_view.render(:partial => Customer.new("lifo"), :locals => {:greeting => "Hello"}) + end + + def test_render_partial_with_object_and_format_uses_render_partial_path + assert_equal "<greeting>Hello</greeting><name>lifo</name>", + @controller_view.render(:partial => Customer.new("lifo"), :formats => :xml, :locals => {:greeting => "Hello"}) + end + def test_render_partial_using_object assert_equal "Hello: lifo", @controller_view.render(Customer.new("lifo"), :greeting => "Hello") @@ -381,7 +391,7 @@ module RenderTestCases def test_render_ignores_templates_with_malformed_template_handlers ActiveSupport::Deprecation.silence do %w(malformed malformed.erb malformed.html.erb malformed.en.html.erb).each do |name| - assert File.exists?(File.expand_path("#{FIXTURE_LOAD_PATH}/test/malformed/#{name}~")), "Malformed file (#{name}~) which should be ignored does not exists" + assert File.exist?(File.expand_path("#{FIXTURE_LOAD_PATH}/test/malformed/#{name}~")), "Malformed file (#{name}~) which should be ignored does not exists" assert_raises(ActionView::MissingTemplate) { @view.render(:file => "test/malformed/#{name}") } end end @@ -444,7 +454,7 @@ module RenderTestCases def test_render_partial_with_layout_raises_descriptive_error e = assert_raises(ActionView::MissingTemplate) { @view.render(partial: 'test/partial', layout: true) } - assert_match "Missing partial /true with", e.message + assert_match "Missing partial /_true with", e.message end def test_render_with_nested_layout diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb index 802da5d566..fb016a52de 100644 --- a/actionview/test/template/tag_helper_test.rb +++ b/actionview/test/template/tag_helper_test.rb @@ -96,6 +96,10 @@ class TagHelperTest < ActionView::TestCase assert_equal "<![CDATA[<hello world>]]>", cdata_section("<hello world>") end + def test_cdata_section_with_string_conversion + assert_equal "<![CDATA[]]>", cdata_section(nil) + end + def test_cdata_section_splitted assert_equal "<![CDATA[hello]]]]><![CDATA[>world]]>", cdata_section("hello]]>world") assert_equal "<![CDATA[hello]]]]><![CDATA[>world]]]]><![CDATA[>again]]>", cdata_section("hello]]>world]]>again") diff --git a/actionview/test/template/template_error_test.rb b/actionview/test/template/template_error_test.rb index 91424daeed..3971ec809c 100644 --- a/actionview/test/template/template_error_test.rb +++ b/actionview/test/template/template_error_test.rb @@ -6,6 +6,13 @@ class TemplateErrorTest < ActiveSupport::TestCase assert_equal "original", error.message end + def test_provides_original_backtrace + original_exception = Exception.new + original_exception.set_backtrace(%W[ foo bar baz ]) + error = ActionView::Template::Error.new("test", original_exception) + assert_equal %W[ foo bar baz ], error.backtrace + end + def test_provides_useful_inspect error = ActionView::Template::Error.new("test", Exception.new("original")) assert_equal "#<ActionView::Template::Error: original>", error.inspect diff --git a/actionview/test/template/testing/fixture_resolver_test.rb b/actionview/test/template/testing/fixture_resolver_test.rb index 9649f349cb..d6cfa997cd 100644 --- a/actionview/test/template/testing/fixture_resolver_test.rb +++ b/actionview/test/template/testing/fixture_resolver_test.rb @@ -3,13 +3,13 @@ require 'abstract_unit' class FixtureResolverTest < ActiveSupport::TestCase def test_should_return_empty_list_for_unknown_path resolver = ActionView::FixtureResolver.new() - templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []}) + templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :variants => [], :handlers => []}) assert_equal [], templates, "expected an empty list of templates" end def test_should_return_template_for_declared_path resolver = ActionView::FixtureResolver.new("arbitrary/path.erb" => "this text") - templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => [:erb]}) + templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :variants => [], :handlers => [:erb]}) assert_equal 1, templates.size, "expected one template" assert_equal "this text", templates.first.source assert_equal "arbitrary/path", templates.first.virtual_path diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb index c2999fcb85..a514bba83d 100644 --- a/actionview/test/template/text_helper_test.rb +++ b/actionview/test/template/text_helper_test.rb @@ -21,6 +21,11 @@ class TextHelperTest < ActionView::TestCase assert 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? + end + def test_simple_format assert_equal "<p></p>", simple_format(nil) @@ -42,6 +47,11 @@ class TextHelperTest < ActionView::TestCase assert_equal "<p><b> test with unsafe string </b></p>", simple_format("<b> test with unsafe string </b><script>code!</script>") end + def test_simple_format_should_sanitize_input_when_sanitize_option_is_true + assert_equal '<p><b> test with unsafe string </b></p>', + simple_format('<b> test with unsafe string </b><script>code!</script>', {}, sanitize: true) + end + def test_simple_format_should_not_sanitize_input_when_sanitize_option_is_false assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :sanitize => false) end @@ -376,6 +386,13 @@ class TextHelperTest < ActionView::TestCase assert_equal("3", cycle("one", 2, "3")) end + def test_cycle_with_array + array = [1, 2, 3] + assert_equal("1", cycle(array)) + assert_equal("2", cycle(array)) + assert_equal("3", cycle(array)) + end + def test_cycle_with_no_arguments assert_raise(ArgumentError) { cycle } end @@ -450,7 +467,7 @@ class TextHelperTest < ActionView::TestCase reset_cycle("colors") end - def test_recet_named_cycle + def test_reset_named_cycle assert_equal("1", cycle(1, 2, 3, :name => "numbers")) assert_equal("red", cycle("red", "blue", :name => "colors")) reset_cycle("numbers") diff --git a/actionview/test/template/text_test.rb b/actionview/test/template/text_test.rb new file mode 100644 index 0000000000..d899d54589 --- /dev/null +++ b/actionview/test/template/text_test.rb @@ -0,0 +1,17 @@ +require 'abstract_unit' + +class TextTest < ActiveSupport::TestCase + test 'formats returns symbol for recognized MIME type' do + assert_equal [:text], ActionView::Template::Text.new('', :text).formats + end + + test 'formats returns string for recognized MIME type when MIME does not have symbol' do + foo = Mime::Type.lookup("foo") + assert_nil foo.to_sym + assert_equal ['foo'], ActionView::Template::Text.new('', foo).formats + end + + test 'formats returns string for unknown MIME type' do + assert_equal ['foo'], ActionView::Template::Text.new('', 'foo').formats + end +end diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb index d496dbb35e..c4770840fb 100644 --- a/actionview/test/template/translation_helper_test.rb +++ b/actionview/test/template/translation_helper_test.rb @@ -31,7 +31,7 @@ class TranslationHelperTest < ActiveSupport::TestCase end def test_delegates_to_i18n_setting_the_rescue_format_option_to_html - I18n.expects(:translate).with(:foo, :locale => 'en', :rescue_format => :html).returns("") + I18n.expects(:translate).with(:foo, :locale => 'en', :raise=>true).returns("") translate :foo, :locale => 'en' end @@ -53,6 +53,22 @@ class TranslationHelperTest < ActiveSupport::TestCase assert_equal false, translate(:"translations.missing", :rescue_format => nil).html_safe? end + def test_raises_missing_translation_message_with_raise_config_option + ActionView::Base.raise_on_missing_translations = true + + assert_raise(I18n::MissingTranslationData) do + translate("translations.missing") + end + ensure + ActionView::Base.raise_on_missing_translations = false + end + + def test_raises_missing_translation_message_with_raise_option + assert_raise(I18n::MissingTranslationData) do + translate(:"translations.missing", :raise => true) + end + end + def test_i18n_translate_defaults_to_nil_rescue_format expected = 'translation missing: en.translations.missing' assert_equal expected, I18n.translate(:"translations.missing") diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb index deba33510a..7e978e15d2 100644 --- a/actionview/test/template/url_helper_test.rb +++ b/actionview/test/template/url_helper_test.rb @@ -56,6 +56,13 @@ class UrlHelperTest < ActiveSupport::TestCase assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com") end + def test_button_to_with_path + assert_dom_equal( + %{<form method="post" action="/article/Hello" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + button_to("Hello", article_path("Hello".html_safe)) + ) + end + def test_button_to_with_straight_url_and_request_forgery self.request_forgery = true |