diff options
Diffstat (limited to 'actionview')
51 files changed, 385 insertions, 196 deletions
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 6240dc6ac6..787e6d68be 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,7 +1,60 @@ -* Only cache template digests if `config.cache_template_loading` id true. +* Use `set_backtrace` instead of instance variable `@backtrace` in ActionView exceptions + + *Shimpei Makimoto* + +* Fix `simple_format` escapes own output when passing `sanitize: true` + + *Paul Seidemann* + +* Ensure `ActionView::Digestor.cache` is correctly cleaned up when + combining recursive templates with `ActionView::Resolver.caching = false`. + + *wyaeld* + +* Fix `collection_check_boxes` generated hidden input to use the name attribute provided + in the options hash. + + *Angel N. Sciortino* + +* Fix some edge cases for AV `select` helper with `:selected` option. + + *Bogdan Gusiev* + +* 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` is 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: diff --git a/actionview/RUNNING_UNIT_TESTS.rdoc b/actionview/RUNNING_UNIT_TESTS.rdoc index a0c35e1810..104b3e288d 100644 --- a/actionview/RUNNING_UNIT_TESTS.rdoc +++ b/actionview/RUNNING_UNIT_TESTS.rdoc @@ -18,7 +18,7 @@ which can be further narrowed down to one test: == Dependency on Active Record and database setup -Test cases in the test/active_record/ directory depend on having +Test cases in the test/activerecord/ directory depend on having activerecord and sqlite installed. If Active Record is not in actionview/../activerecord directory, or the sqlite rubygem is not installed, these tests are skipped. diff --git a/actionview/Rakefile b/actionview/Rakefile index e1cb4b5be0..d56fe9ea76 100644 --- a/actionview/Rakefile +++ b/actionview/Rakefile @@ -11,7 +11,7 @@ task :test => ["test:template", "test:integration:action_pack", "test:integratio namespace :test do task :isolated do - Dir.glob("test/{active_record,template}/**/*_test.rb").all? do |file| + Dir.glob("test/{actionpack,activerecord,template}/**/*_test.rb").all? do |file| sh(Gem.ruby, '-w', '-Ilib:test', file) end or raise "Failures" end diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec index 87cb568300..cdac074973 100644 --- a/actionview/actionview.gemspec +++ b/actionview/actionview.gemspec @@ -5,7 +5,7 @@ Gem::Specification.new do |s| s.name = 'actionview' s.version = version s.summary = 'Rendering framework putting the V in MVC (part of Rails).' - s.description = '' + s.description = 'Simple, battle-tested conventions and helpers for building web pages.' s.required_ruby_version = '>= 1.9.3' diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 95c3200c81..5570e2a8dc 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -10,7 +10,10 @@ module ActionView class << self def digest(name, format, finder, options = {}) - cache_key = ([name, format] + Array.wrap(options[:dependencies])).join('.') + details_key = finder.details_key.hash + dependencies = Array.wrap(options[:dependencies]) + cache_key = ([name, details_key, format] + 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 @@ -32,13 +35,13 @@ module ActionView Digestor end + digest = klass.new(name, format, finder, options).digest # Store the actual digest if config.cache_template_loading is true - klass.new(name, format, finder, options).digest.tap do |digest| - @@cache[cache_key] = digest if ActionView::Resolver.caching? - end - rescue Exception - @@cache.delete_pair(cache_key, false) if pre_stored # something went wrong, make sure not to corrupt the @@cache - raise + @@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 end end diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index a13d0021ea..2a44ae5d5c 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -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', { @@ -224,14 +224,14 @@ module ActionView # # ==== Examples # - # image_tag('rails.png') - # # => <img alt="Rails" src="/assets/rails.png" /> + # image_alt('rails.png') + # # => Rails # - # image_tag('hyphenated-file-name.png') - # # => <img alt="Hyphenated file name" src="/assets/hyphenated-file-name.png" /> + # image_alt('hyphenated-file-name.png') + # # => Hyphenated file name # - # image_tag('underscored_file_name.png') - # # => <img alt="Underscored file name" src="/assets/underscored_file_name.png" /> + # image_alt('underscored_file_name.png') + # # => Underscored file name def image_alt(src) File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').tr('-_', ' ').capitalize end diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb index 0b957adb91..c830ab23e3 100644 --- a/actionview/lib/action_view/helpers/asset_url_helper.rb +++ b/actionview/lib/action_view/helpers/asset_url_helper.rb @@ -143,9 +143,9 @@ module ActionView "#{source}#{tail}" end - alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with a asset_path named route + alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with an asset_path named route - # Computes the full URL to a asset in the public directory. This + # Computes the full URL to an asset in the public directory. This # will use +asset_path+ internally, so most of their behaviors # will be the same. def asset_url(source, options = {}) 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 2a38e5c446..b3af1d4da4 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -4,7 +4,7 @@ module ActionView module CacheHelper # This helper exposes a method for caching fragments of a view # rather than an entire action or page. This technique is useful - # caching pieces like menus, lists of newstopics, static HTML + # caching pieces like menus, lists of new topics, static HTML # fragments, and so on. This method takes a block that contains # the content you wish to cache. # diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index 8e1aea50a9..36e4c5725f 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -50,19 +50,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 = { @@ -531,7 +531,7 @@ module ActionView # my_date = Time.now + 2.days # # # Generates a select field for days that defaults to the day for the date in my_date. - # select_day(my_time) + # select_day(my_date) # # # Generates a select field for days that defaults to the number given. # select_day(5) @@ -541,7 +541,7 @@ module ActionView # # # Generates a select field for days that defaults to the day for the date in my_date # # that is named 'due' rather than 'day'. - # select_day(my_time, field_name: 'due') + # select_day(my_date, field_name: 'due') # # # Generates a select field for days with a custom prompt. Use <tt>prompt: true</tt> for a # # generic prompt. diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index 8a4830d887..38d969ed0c 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -442,10 +442,11 @@ module ActionView object = convert_to_model(object) as = options[:as] + namespace = options[:namespace] action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post] options[:html].reverse_merge!( class: as ? "#{action}_#{as}" : dom_class(object, action), - id: as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence, + id: (as ? [namespace, action, as] : [namespace, dom_id(object, action)]).compact.join("_").presence, method: method ) @@ -1172,7 +1173,7 @@ module ActionView # methods in the +FormHelper+ module. This class, however, allows you to # call methods with the model object you are building the form for. # - # You can create your own custom FormBuilder templates by subclasses this + # You can create your own custom FormBuilder templates by subclassing this # class. For example: # # class MyFormBuilder < ActionView::Helpers::FormBuilder diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb index fcd151ac32..4347983bad 100644 --- a/actionview/lib/action_view/helpers/form_options_helper.rb +++ b/actionview/lib/action_view/helpers/form_options_helper.rb @@ -128,6 +128,15 @@ module ActionView # or <tt>selected: nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option # tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled. # + # A block can be passed to +select+ to customize how the options tags will be rendered. This + # is useful when the options tag has complex attributes. + # + # 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 + # # ==== Gotcha # # The HTML specification says when +multiple+ parameter passed to select and all options got deselected @@ -152,8 +161,8 @@ module ActionView # In case if you don't want the helper to generate this hidden field you can specify # <tt>include_hidden: false</tt> option. # - def select(object, method, choices, options = {}, html_options = {}) - Tags::Select.new(object, method, self, choices, options, html_options).render + def select(object, method, choices = nil, options = {}, html_options = {}, &block) + Tags::Select.new(object, method, self, choices, options, html_options, &block).render end # Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of @@ -766,8 +775,8 @@ module ActionView # <% end %> # # Please refer to the documentation of the base helper for details. - def select(method, choices, options = {}, html_options = {}) - @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options)) + def select(method, choices = nil, options = {}, html_options = {}, &block) + @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options), &block) end # Wraps ActionView::Helpers::FormOptionsHelper#collection_select for form builders: diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb index fda7038a5d..9adc2c1a8f 100644 --- a/actionview/lib/action_view/helpers/number_helper.rb +++ b/actionview/lib/action_view/helpers/number_helper.rb @@ -105,12 +105,7 @@ module ActionView # number_to_currency(1234567890.50, unit: "£", separator: ",", delimiter: "", format: "%n %u") # # => 1234567890,50 £ 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,15 +377,20 @@ module ActionView # number_to_human(0.34, units: :distance) # => "34 centimeters" # def number_to_human(number, options = {}) + 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_delimiters_and_separators(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? diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb index 732f35643a..3528381781 100644 --- a/actionview/lib/action_view/helpers/tag_helper.rb +++ b/actionview/lib/action_view/helpers/tag_helper.rb @@ -114,7 +114,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 +151,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/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index 3fe3f4e9df..8607da301c 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -119,7 +119,8 @@ module ActionView html_options = html_options.stringify_keys add_default_name_and_id(html_options) options[:include_blank] ||= true unless options[:prompt] || select_not_required?(html_options) - select = content_tag("select", add_options(option_tags, options, value(object)), html_options) + value = options.fetch(:selected) { value(object) } + select = content_tag("select", add_options(option_tags, options, value), html_options) if html_options["multiple"] && options.fetch(:include_hidden, true) tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb index 52006d856b..9b77ebeb1b 100644 --- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb +++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb @@ -27,7 +27,8 @@ module ActionView # Append a hidden field to make sure something will be sent back to the # server if all check boxes are unchecked. - hidden = @template_object.hidden_field_tag("#{tag_name}[]", "", :id => nil) + hidden_name = @html_options[:name] || "#{tag_name}[]" + hidden = @template_object.hidden_field_tag(hidden_name, "", :id => nil) rendered_collection + hidden end diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb index 388dcf1f13..787039c82e 100644 --- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb +++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb @@ -18,7 +18,8 @@ module ActionView end def label(label_html_options={}, &block) - @template_object.label(@object_name, @sanitized_attribute_name, @text, label_html_options, &block) + html_options = label_html_options.merge(@input_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 35d3ba8434..180aa9ac27 100644 --- a/actionview/lib/action_view/helpers/tags/label.rb +++ b/actionview/lib/action_view/helpers/tags/label.rb @@ -30,6 +30,7 @@ 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? diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb index d64e2f68ef..00881d9978 100644 --- a/actionview/lib/action_view/helpers/tags/select.rb +++ b/actionview/lib/action_view/helpers/tags/select.rb @@ -3,8 +3,9 @@ module ActionView module Tags # :nodoc: class Select < Base # :nodoc: def initialize(object_name, method_name, template_object, choices, options, html_options) - @choices = choices + @choices = block_given? ? template_object.capture { yield } : choices @choices = @choices.to_a if @choices.is_a?(Range) + @html_options = html_options super(object_name, method_name, template_object, options) diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb index 3fc64fa8a5..b0e4aa3cd3 100644 --- a/actionview/lib/action_view/helpers/text_helper.rb +++ b/actionview/lib/action_view/helpers/text_helper.rb @@ -150,7 +150,7 @@ module ActionView def excerpt(text, phrase, options = {}) return unless text && phrase - separator = options.fetch(:separator, "") + separator = options[:separator] || '' phrase = Regexp.escape(phrase) regex = /#{phrase}/i @@ -171,7 +171,8 @@ module ActionView prefix, first_part = cut_excerpt_part(:first, first_part, separator, options) postfix, second_part = cut_excerpt_part(:second, second_part, separator, options) - prefix + (first_part + separator + phrase + separator + second_part).strip + postfix + affix = [first_part, separator, phrase, separator, second_part].join.strip + [prefix, affix, postfix].join end # Attempts to pluralize the +singular+ word unless +count+ is 1. If @@ -267,7 +268,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, paragraph, html_options, false) }.join("\n\n").html_safe end end diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 1920a94567..56dd7a4390 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -92,8 +92,9 @@ module ActionView # ==== Data attributes # # * <tt>confirm: 'question?'</tt> - This will allow the unobtrusive JavaScript - # driver to prompt with the question specified. If the user accepts, the link is - # processed normally, otherwise no action is taken. + # driver to prompt with the question specified (in this case, the + # resulting text would be <tt>question?</tt>. If the user accepts, the + # link is processed normally, otherwise no action is taken. # * <tt>:disable_with</tt> - Value of this parameter will be # used as the value for a disabled version of the submit # button when the form is submitted. This feature is provided @@ -213,6 +214,7 @@ module ActionView # * <tt>:form</tt> - This hash will be form attributes # * <tt>:form_class</tt> - This controls the class of the form within which the submit button will # be placed + # * <tt>:params</tt> - Hash of parameters to be rendered as hidden fields within the form. # # ==== Data attributes # @@ -287,6 +289,7 @@ module ActionView url = options.is_a?(String) ? options : url_for(options) remote = html_options.delete('remote') + params = html_options.delete('params') method = html_options.delete('method').to_s method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : ''.html_safe @@ -310,6 +313,11 @@ module ActionView end inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag) + if params + 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) end @@ -454,7 +462,7 @@ module ActionView html_options, name = name, nil if block_given? html_options = (html_options || {}).stringify_keys - extras = %w{ cc bcc body subject }.map { |item| + extras = %w{ cc bcc body subject }.map! { |item| option = html_options.delete(item) || next "#{item}=#{Rack::Utils.escape_path(option)}" }.compact diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb index d8de1d95df..ffa67649da 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: 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/railtie.rb b/actionview/lib/action_view/railtie.rb index 4113448af0..c2783f6377 100644 --- a/actionview/lib/action_view/railtie.rb +++ b/actionview/lib/action_view/railtie.rb @@ -48,5 +48,9 @@ module ActionView ActionMailer::Base.send(:include, ActionView::Layouts) end end + + rake_tasks do + load "action_view/tasks/dependencies.rake" + end end 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/rendering.rb b/actionview/lib/action_view/rendering.rb index db9c4ef501..82db9e26df 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -77,50 +77,29 @@ module ActionView @_view_renderer ||= ActionView::Renderer.new(lookup_context) end - # Render template to response_body - # :api: public - def render(*args, &block) - options = _normalize_render(*args, &block) - self.response_body = render_to_body(options) - end - - # Raw rendering of a template to a string. - # :api: public - def render_to_string(*args, &block) - options = _normalize_render(*args, &block) - render_to_body(options) - end - - # Raw rendering of a template. - # :api: public def render_to_body(options = {}) _process_options(options) _render_template(options) 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 rendered_format Mime[lookup_context.rendered_format] end - def default_protected_instance_vars - super.concat([:@_view_context_class, :@_view_renderer, :@_lookup_context]) - end - private - # Normalize args and options. + # Find and renders a template based on the options given. # :api: private - def _normalize_render(*args, &block) - options = _normalize_args(*args, &block) - _normalize_options(options) - options + def _render_template(options) #:nodoc: + lookup_context.rendered_format = nil if options[:formats] + view_renderer.render(view_context, options) + end + + # Assign the rendered format to lookup context. + def _process_format(format) #:nodoc: + super + lookup_context.formats = [format.to_sym] + lookup_context.rendered_format = lookup_context.formats.first end # Normalize args by converting render "foo" to render :action => "foo" and 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/tasks/dependencies.rake b/actionview/lib/action_view/tasks/dependencies.rake new file mode 100644 index 0000000000..1b9426c0e5 --- /dev/null +++ b/actionview/lib/action_view/tasks/dependencies.rake @@ -0,0 +1,17 @@ +namespace :cache_digests do + desc 'Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)' + task :nested_dependencies => :environment do + abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present? + template, format = ENV['TEMPLATE'].split(".") + format ||= :html + puts JSON.pretty_generate ActionView::Digestor.new(template, format, ApplicationController.new.lookup_context).nested_dependencies + end + + desc 'Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)' + task :dependencies => :environment do + abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present? + template, format = ENV['TEMPLATE'].split(".") + format ||= :html + puts JSON.pretty_generate ActionView::Digestor.new(template, format, ApplicationController.new.lookup_context).dependencies + end +end diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index e2c50fec47..9b0619f1aa 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -142,7 +142,7 @@ module ActionView compile!(view) view.send(method_name, locals, buffer, &block) end - rescue Exception => e + rescue => e handle_render_error(view, e) end @@ -294,7 +294,7 @@ module ActionView begin mod.module_eval(source, identifier, 0) ObjectSpace.define_finalizer(self, Finalizer[method_name, mod]) - rescue Exception => e # errors from template code + rescue => e # errors from template code if logger = (view && view.logger) logger.debug "ERROR: compiling #{method_name} RAISED #{e}" logger.debug "Function body: #{source}" diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb index a89d51221e..7b4b5e13e0 100644 --- a/actionview/lib/action_view/template/error.rb +++ b/actionview/lib/action_view/template/error.rb @@ -56,13 +56,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/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/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/test/actionpack/abstract/helper_test.rb b/actionview/test/actionpack/abstract/helper_test.rb index 4e05245584..89c4567715 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 diff --git a/actionview/test/lib/controller/view_paths_test.rb b/actionview/test/actionpack/controller/view_paths_test.rb index c6e7a523b9..c6e7a523b9 100644 --- a/actionview/test/lib/controller/view_paths_test.rb +++ b/actionview/test/actionpack/controller/view_paths_test.rb diff --git a/actionview/test/fixtures/helpers/abc_helper.rb b/actionview/test/fixtures/helpers/abc_helper.rb index 7104ff3730..cf2774bb5f 100644 --- a/actionview/test/fixtures/helpers/abc_helper.rb +++ b/actionview/test/fixtures/helpers/abc_helper.rb @@ -1,5 +1,3 @@ module AbcHelper def bare_a() end - def bare_b() end - def bare_c() end end diff --git a/actionview/test/fixtures/helpers/fun/games_helper.rb b/actionview/test/fixtures/helpers/fun/games_helper.rb deleted file mode 100644 index 3b7adce086..0000000000 --- a/actionview/test/fixtures/helpers/fun/games_helper.rb +++ /dev/null @@ -1,5 +0,0 @@ -module Fun - module GamesHelper - def stratego() "Iz guuut!" end - end -end
\ No newline at end of file diff --git a/actionview/test/fixtures/helpers/fun/pdf_helper.rb b/actionview/test/fixtures/helpers/fun/pdf_helper.rb deleted file mode 100644 index 0171be8500..0000000000 --- a/actionview/test/fixtures/helpers/fun/pdf_helper.rb +++ /dev/null @@ -1,5 +0,0 @@ -module Fun - module PdfHelper - def foobar() 'baz' end - end -end diff --git a/actionview/test/fixtures/helpers/just_me_helper.rb b/actionview/test/fixtures/helpers/just_me_helper.rb deleted file mode 100644 index b140a7b9b4..0000000000 --- a/actionview/test/fixtures/helpers/just_me_helper.rb +++ /dev/null @@ -1,3 +0,0 @@ -module JustMeHelper - def me() "mine!" end -end
\ No newline at end of file diff --git a/actionview/test/fixtures/helpers/me_too_helper.rb b/actionview/test/fixtures/helpers/me_too_helper.rb deleted file mode 100644 index ce56042143..0000000000 --- a/actionview/test/fixtures/helpers/me_too_helper.rb +++ /dev/null @@ -1,3 +0,0 @@ -module MeTooHelper - def me() "me too!" end -end
\ No newline at end of file diff --git a/actionview/test/lib/controller/fake_controllers.rb b/actionview/test/lib/controller/fake_controllers.rb deleted file mode 100644 index 1a2863b689..0000000000 --- a/actionview/test/lib/controller/fake_controllers.rb +++ /dev/null @@ -1,35 +0,0 @@ -class ContentController < ActionController::Base; end - -module Admin - class AccountsController < ActionController::Base; end - class PostsController < ActionController::Base; end - class StuffController < ActionController::Base; end - class UserController < ActionController::Base; end - class UsersController < ActionController::Base; end -end - -module Api - class UsersController < ActionController::Base; end - class ProductsController < ActionController::Base; end -end - -class AccountController < ActionController::Base; end -class ArchiveController < ActionController::Base; end -class ArticlesController < ActionController::Base; end -class BarController < ActionController::Base; end -class BlogController < ActionController::Base; end -class BooksController < ActionController::Base; end -class CarsController < ActionController::Base; end -class CcController < ActionController::Base; end -class CController < ActionController::Base; end -class FooController < ActionController::Base; end -class GeocodeController < ActionController::Base; end -class NewsController < ActionController::Base; end -class NotesController < ActionController::Base; end -class PagesController < ActionController::Base; end -class PeopleController < ActionController::Base; end -class PostsController < ActionController::Base; end -class SubpathBooksController < ActionController::Base; end -class SymbolsController < ActionController::Base; end -class UserController < ActionController::Base; end -class UsersController < ActionController::Base; end diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb index c6608e214a..779a7fb53c 100644 --- a/actionview/test/template/digestor_test.rb +++ b/actionview/test/template/digestor_test.rb @@ -15,6 +15,16 @@ end class FixtureFinder FIXTURES_DIR = "#{File.dirname(__FILE__)}/../fixtures/digestor" + attr_reader :details + + def initialize + @details = {} + 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") end @@ -140,6 +150,20 @@ class TemplateDigestorTest < ActionView::TestCase end end + def test_details_are_included_in_cache_key + # Cache the template digest. + old_digest = digest("events/_event") + + # Change the template; the cached digest remains unchanged. + change_template("events/_event") + + # The details are changed, so a new cache key is generated. + finder.details[:foo] = "bar" + + # The cache is busted. + assert_not_equal old_digest, digest("events/_event") + end + def test_extra_whitespace_in_render_partial assert_digest_difference("messages/edit") do change_template("messages/_form") @@ -193,6 +217,31 @@ class TemplateDigestorTest < ActionView::TestCase ActionView::Resolver.caching = resolver_before end + def test_digest_cache_cleanup_with_recursion + first_digest = digest("level/_recursion") + second_digest = digest("level/_recursion") + + assert first_digest + + # If the cache is cleaned up correctly, subsequent digests should return the same + assert_equal first_digest, second_digest + end + + def test_digest_cache_cleanup_with_recursion_and_template_caching_off + resolver_before = ActionView::Resolver.caching + ActionView::Resolver.caching = false + + first_digest = digest("level/_recursion") + second_digest = digest("level/_recursion") + + assert first_digest + + # If the cache is cleaned up correctly, subsequent digests should return the same + assert_equal first_digest, second_digest + ensure + ActionView::Resolver.caching = resolver_before + end + private def assert_logged(message) old_logger = ActionView::Base.logger @@ -220,7 +269,11 @@ class TemplateDigestorTest < ActionView::TestCase end def digest(template_name, options={}) - ActionView::Digestor.digest(template_name, :html, FixtureFinder.new, options) + ActionView::Digestor.digest(template_name, :html, finder, options) + end + + def finder + @finder ||= FixtureFinder.new end def change_template(template_name) diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb index bc9c21dfd3..d28e4aeb48 100644 --- a/actionview/test/template/form_collections_helper_test.rb +++ b/actionview/test/template/form_collections_helper_test.rb @@ -17,14 +17,14 @@ class FormCollectionsHelperTest < ActionView::TestCase end # COLLECTION RADIO BUTTONS - test 'collection radio accepts a collection and generate inputs from value method' do + test 'collection radio accepts a collection and generates inputs from value method' do with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s assert_select 'input[type=radio][value=true]#user_active_true' assert_select 'input[type=radio][value=false]#user_active_false' end - test 'collection radio accepts a collection and generate inputs from label method' do + test 'collection radio accepts a collection and generates inputs from label method' do with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s assert_select 'label[for=user_active_true]', 'true' @@ -38,7 +38,7 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select 'label[for=user_active_no]', 'No' end - test 'colection radio should sanitize collection values for labels correctly' do + test 'collection radio should sanitize collection values for labels correctly' do with_collection_radio_buttons :user, :name, ['$0.99', '$1.99'], :to_s, :to_s assert_select 'label[for=user_name_099]', '$0.99' assert_select 'label[for=user_name_199]', '$1.99' @@ -179,6 +179,13 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select "input[type=hidden][name='user[category_ids][]'][value=]", :count => 1 end + test 'collection check boxes generates a hidden field using the given :name in :html_options' do + collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + with_collection_check_boxes :user, :category_ids, collection, :id, :name, {}, {name: "user[other_category_ids][]"} + + assert_select "input[type=hidden][name='user[other_category_ids][]'][value=]", :count => 1 + end + test 'collection check boxes accepts a collection and generate a serie 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 @@ -194,7 +201,7 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select 'label[for=user_active_no]', 'No' end - test 'colection check box should sanitize collection values for labels correctly' do + test 'collection check box should sanitize collection values for labels correctly' do with_collection_check_boxes :user, :name, ['$0.99', '$1.99'], :to_s, :to_s assert_select 'label[for=user_name_099]', '$0.99' assert_select 'label[for=user_name_199]', '$1.99' diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index 8cca43d7ca..3e8a2468ed 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -1282,6 +1282,24 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_with_namespace_and_with_collection_radio_buttons + post = Post.new + def post.active; false; end + + form_for(post, namespace: 'foo') do |f| + concat f.collection_radio_buttons(:active, [true, false], :to_s, :to_s) + end + + expected = whole_form("/posts", "foo_new_post", "new_post") do + "<input id='foo_post_active_true' name='post[active]' type='radio' value='true' />" + + "<label for='foo_post_active_true'>true</label>" + + "<input checked='checked' id='foo_post_active_false' name='post[active]' type='radio' value='false' />" + + "<label for='foo_post_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 @@ -1361,6 +1379,24 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_with_namespace_and_with_collection_check_boxes + post = Post.new + def post.tag_ids; [1]; end + collection = [[1, "Tag 1"]] + + form_for(post, namespace: 'foo') do |f| + concat f.collection_check_boxes(:tag_ids, collection, :first, :last) + end + + expected = whole_form("/posts", "foo_new_post", "new_post") do + "<input checked='checked' id='foo_post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" + + "<label for='foo_post_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 @@ -1681,6 +1717,18 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_for_with_namespace_and_as_option + form_for(@post, namespace: 'namespace', as: 'custom_name') do |f| + concat f.text_field(:title) + end + + expected = whole_form('/posts/123', 'namespace_edit_custom_name', 'edit_custom_name', method: 'patch') do + "<input id='namespace_custom_name_title' name='custom_name[title]' type='text' value='Hello World' />" + end + + assert_dom_equal expected, output_buffer + end + def test_two_form_for_with_namespace form_for(@post, namespace: 'namespace_1') do |f| concat f.label(:title) diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb index 3ec138b639..50e9d132a7 100644 --- a/actionview/test/template/form_options_helper_test.rb +++ b/actionview/test/template/form_options_helper_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'tzinfo' class Map < Hash def category @@ -7,8 +6,6 @@ class Map < Hash end end -TZInfo::Timezone.cattr_reader :loaded_zones - class FormOptionsHelperTest < ActionView::TestCase tests ActionView::Helpers::FormOptionsHelper @@ -22,7 +19,7 @@ class FormOptionsHelperTest < ActionView::TestCase def setup @fake_timezones = %w(A B C D E).map do |id| - tz = TZInfo::Timezone.loaded_zones[id] = stub(:name => id, :to_s => id) + tz = stub(:name => id, :to_s => id) ActiveSupport::TimeZone.stubs(:[]).with(id).returns(tz) tz end @@ -559,6 +556,21 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_select_under_fields_for_with_block + @post = Post.new + + output_buffer = fields_for :post, @post do |f| + concat(f.select(:category) do + concat content_tag(:option, "hello world") + end) + end + + assert_dom_equal( + "<select id=\"post_category\" name=\"post[category]\"><option>hello world</option></select>", + output_buffer + ) + end + def test_select_with_multiple_to_add_hidden_input output_buffer = select(:post, :category, "", {}, :multiple => true) assert_dom_equal( @@ -786,6 +798,22 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_select_not_existing_method_with_selected_value + @post = Post.new + assert_dom_equal( + "<select id=\"post_locale\" name=\"post[locale]\"><option value=\"en\">en</option>\n<option value=\"ru\" selected=\"selected\">ru</option></select>", + select("post", "locale", %w( en ru ), :selected => 'ru') + ) + end + + def test_select_with_prompt_and_selected_value + @post = Post.new + assert_dom_equal( + "<select id=\"post_category\" name=\"post[category]\"><option value=\"one\">one</option>\n<option selected=\"selected\" value=\"two\">two</option></select>", + select("post", "category", %w( one two ), :selected => 'two', :prompt => true) + ) + end + def test_select_with_disabled_array @post = Post.new @post.category = "<mus>" diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index 928dfb092d..5a7d11f513 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -381,7 +381,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 diff --git a/actionview/test/template/resolver_patterns_test.rb b/actionview/test/template/resolver_patterns_test.rb index 97b1bad055..575eb9bd28 100644 --- a/actionview/test/template/resolver_patterns_test.rb +++ b/actionview/test/template/resolver_patterns_test.rb @@ -20,7 +20,7 @@ class ResolverPatternsTest < ActiveSupport::TestCase assert_equal [:html], templates.first.formats end - def test_should_return_all_templates_when_ambigous_pattern + def test_should_return_all_templates_when_ambiguous_pattern templates = @resolver.find_all("another", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]}) assert_equal 2, templates.size, "expected two templates" assert_equal "Another template!", templates[0].source 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/text_helper_test.rb b/actionview/test/template/text_helper_test.rb index 1b2234f4e2..c624326683 100644 --- a/actionview/test/template/text_helper_test.rb +++ b/actionview/test/template/text_helper_test.rb @@ -42,6 +42,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 @@ -314,6 +319,9 @@ class TextHelperTest < ActionView::TestCase options = { :separator => "\n", :radius => 1 } assert_equal("...very\nvery long\nstring", excerpt("my very\nvery\nvery long\nstring", 'long', options)) + + assert_equal excerpt('This is a beautiful morning', 'a'), + excerpt('This is a beautiful morning', 'a', separator: nil) end def test_word_wrap diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb index d512fa9913..deba33510a 100644 --- a/actionview/test/template/url_helper_test.rb +++ b/actionview/test/template/url_helper_test.rb @@ -1,5 +1,6 @@ # encoding: utf-8 require 'abstract_unit' +require 'minitest/mock' class UrlHelperTest < ActiveSupport::TestCase @@ -160,6 +161,13 @@ class UrlHelperTest < ActiveSupport::TestCase ) end + def test_button_to_with_params + assert_dom_equal( + %{<form action="http://www.example.com" class="button_to" method="post"><div><input type="submit" value="Hello" /><input type="hidden" name="foo" value="bar" /><input type="hidden" name="baz" value="quux" /></div></form>}, + button_to("Hello", "http://www.example.com", params: {foo: :bar, baz: "quux"}) + ) + end + def test_link_tag_with_straight_url assert_dom_equal %{<a href="http://www.example.com">Hello</a>}, link_to("Hello", "http://www.example.com") end |