diff options
Diffstat (limited to 'actionview')
20 files changed, 207 insertions, 52 deletions
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index e2cd9f7558..5e17e65bde 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,9 @@ +* Ensure unique DOM IDs for collection inputs with float values. + Fixes #34974 + + *Mark Edmondson* + + ## Rails 6.0.0.beta1 (January 18, 2019) ## * [Rename npm package](https://github.com/rails/rails/pull/34905) from diff --git a/actionview/Rakefile b/actionview/Rakefile index 7851a2b6bf..237e458b6f 100644 --- a/actionview/Rakefile +++ b/actionview/Rakefile @@ -31,9 +31,26 @@ namespace :test do desc "Run tests for rails-ujs" task :ujs do + system("npm run lint") + exit $?.exitstatus unless $?.success? + begin + listen_host = "localhost" + listen_port = "4567" + + runner_command = %w(ruby ../ci/qunit-selenium-runner.rb) + if ENV["SELENIUM_DRIVER_URL"] + require "socket" + runner_command += %W(http://#{Socket.gethostname}:#{listen_port}/ #{ENV["SELENIUM_DRIVER_URL"]}) + listen_host = "0.0.0.0" + else + runner_command += %W(http://localhost:#{listen_port}/) + end + Dir.mkdir("log") - pid = spawn("bundle exec rackup test/ujs/config.ru -p 4567 -s puma > log/test.log 2>&1", pgroup: true) + pid = File.open("log/test.log", "w") do |f| + spawn(*%W(rackup test/ujs/config.ru -o #{listen_host} -p #{listen_port} -s puma), out: f, err: f, pgroup: true) + end start_time = Time.now @@ -41,12 +58,16 @@ namespace :test do break if system("lsof -i :4567", 1 => File::NULL) if Time.now - start_time > 5 - puts "Timed out after 5 seconds" + puts "Failed to start puma after 5 seconds" + puts + puts File.read("log/test.log") exit 1 end + + sleep 0.2 end - system("npm run lint && bundle exec ruby ../ci/qunit-selenium-runner.rb http://localhost:4567/") + system(*runner_command) status = $?.exitstatus ensure Process.kill("KILL", -pid) if pid diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb index c4a614fa6d..6ff2d70e35 100644 --- a/actionview/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -45,6 +45,7 @@ module ActionView autoload :Rendering autoload :RoutingUrlFor autoload :Template + autoload :FileTemplate autoload :ViewPaths autoload_under "renderer" do diff --git a/actionview/lib/action_view/file_template.rb b/actionview/lib/action_view/file_template.rb new file mode 100644 index 0000000000..4921074383 --- /dev/null +++ b/actionview/lib/action_view/file_template.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "action_view/template" + +module ActionView + class FileTemplate < Template + def initialize(filename, handler, details) + @filename = filename + + super(nil, filename, handler, details) + end + + def source + File.binread @filename + end + + def refresh(_) + self + end + + # Exceptions are marshalled when using the parallel test runner with DRb, so we need + # to ensure that references to the template object can be marshalled as well. This means forgoing + # the marshalling of the compiler mutex and instantiating that again on unmarshalling. + def marshal_dump # :nodoc: + [ @identifier, @handler, @compiled, @original_encoding, @locals, @virtual_path, @updated_at, @formats, @variants ] + end + + def marshal_load(array) # :nodoc: + @identifier, @handler, @compiled, @original_encoding, @locals, @virtual_path, @updated_at, @formats, @variants = *array + @compile_mutex = Mutex.new + end + end +end diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index eef527d36f..0adecf362a 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -138,7 +138,7 @@ module ActionView end def sanitized_value(value) - value.to_s.gsub(/\s/, "_").gsub(/[^-[[:word:]]]/, "").mb_chars.downcase.to_s + value.to_s.gsub(/[\s\.]/, "_").gsub(/[^-[[:word:]]]/, "").mb_chars.downcase.to_s end def select_content_tag(option_tags, options, html_options) diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index ae1c93e12f..67c86592b9 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -138,7 +138,7 @@ module ActionView end def html_safe_translation_key?(key) - /(\b|_|\.)html$/.match?(key.to_s) + /([_.]|\b)html\z/.match?(key.to_s) end end end diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index 478400e016..801916954f 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -295,8 +295,21 @@ module ActionView end def render(context, options, block) - setup(context, options, block) - template = find_partial + as = as_variable(options) + setup(context, options, as, block) + + if @path + if @has_object || @collection + @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as) + @template_keys = retrieve_template_keys(@variable) + else + @template_keys = @locals.keys + end + template = find_partial(@path, @template_keys) + @variable ||= template.variable + else + template = nil + end @lookup_context.rendered_format ||= begin if template && template.formats.first @@ -359,7 +372,7 @@ module ActionView # If +options[:partial]+ is a string, then the <tt>@path</tt> instance variable is # set to that string. Otherwise, the +options[:partial]+ object must # respond to +to_partial_path+ in order to setup the path. - def setup(context, options, block) + def setup(context, options, as, block) @options = options @block = block @@ -382,25 +395,25 @@ module ActionView if @collection paths = @collection_data = @collection.map { |o| partial_path(o, context) } - @path = paths.uniq.one? ? paths.first : nil + if paths.uniq.length == 1 + @path = paths.first + else + paths.map! { |path| retrieve_variable(path, as).unshift(path) } + @path = nil + end else @path = partial_path(@object, context) end end + self + end + + def as_variable(options) if as = options[:as] raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s) - as = as.to_sym + as.to_sym end - - if @path - @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as) - @template_keys = retrieve_template_keys - else - paths.map! { |path| retrieve_variable(path, as).unshift(path) } - end - - self end def collection_from_options @@ -414,8 +427,8 @@ module ActionView @object.to_ary if @object.respond_to?(:to_ary) end - def find_partial - find_template(@path, @template_keys) if @path + def find_partial(path, template_keys) + find_template(path, template_keys) end def find_template(path, locals) @@ -511,9 +524,9 @@ module ActionView end end - def retrieve_template_keys + def retrieve_template_keys(variable) keys = @locals.keys - keys << @variable if @has_object || @collection + keys << variable if @collection keys << @variable_counter keys << @variable_iteration diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index 8ce8053011..8a5407c622 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -3,6 +3,7 @@ require "active_support/core_ext/object/try" require "active_support/core_ext/kernel/singleton_class" require "thread" +require "delegate" module ActionView # = Action View Template @@ -127,6 +128,8 @@ module ActionView end end + attr_reader :variable + def initialize(source, identifier, handler, details) format = details[:format] || (handler.default_format if handler.respond_to?(:default_format)) @@ -137,6 +140,13 @@ module ActionView @original_encoding = nil @locals = details[:locals] || [] @virtual_path = details[:virtual_path] + + @variable = if @virtual_path + base = @virtual_path[-1] == "/" ? "" : File.basename(@virtual_path) + base =~ /\A_?(.*?)(?:\.\w+)*\z/ + $1.to_sym + end + @updated_at = details[:updated_at] || Time.now @formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f } @variants = [details[:variant]] @@ -202,7 +212,9 @@ module ActionView # before passing the source on to the template engine, leaving a # blank line in its stead. def encode! - return unless source.encoding == Encoding::BINARY + source = self.source + + return source unless source.encoding == Encoding::BINARY # Look for # encoding: *. If we find one, we'll encode the # String in that encoding, otherwise, we'll use the @@ -277,6 +289,15 @@ module ActionView end end + class LegacyTemplate < DelegateClass(Template) # :nodoc: + attr_reader :source + + def initialize(template, source) + super(template) + @source = source + end + end + # Among other things, this method is responsible for properly setting # the encoding of the compiled template. # @@ -290,8 +311,8 @@ module ActionView # In general, this means that templates will be UTF-8 inside of Rails, # regardless of the original source encoding. def compile(mod) - encode! - code = @handler.call(self) + source = encode! + code = @handler.call(self, source) # Make sure that the resulting String to be eval'd is in the # encoding of the code @@ -312,7 +333,7 @@ module ActionView # handler is valid in the default_internal. This is for handlers # that handle encoding but screw up unless source.valid_encoding? - raise WrongEncodingError.new(@source, Encoding.default_internal) + raise WrongEncodingError.new(source, Encoding.default_internal) end mod.module_eval(source, identifier, 0) diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb index 7ec76dcc3f..ddaac7a100 100644 --- a/actionview/lib/action_view/template/handlers.rb +++ b/actionview/lib/action_view/template/handlers.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "active_support/deprecation" + module ActionView #:nodoc: # = Action View Template Handlers class Template #:nodoc: @@ -14,7 +16,7 @@ module ActionView #:nodoc: base.register_template_handler :erb, ERB.new base.register_template_handler :html, Html.new base.register_template_handler :builder, Builder.new - base.register_template_handler :ruby, :source.to_proc + base.register_template_handler :ruby, lambda { |_, source| source } end @@template_handlers = {} @@ -24,11 +26,35 @@ module ActionView #:nodoc: @@template_extensions ||= @@template_handlers.keys end + class LegacyHandlerWrapper < SimpleDelegator # :nodoc: + def call(view, source) + __getobj__.call(ActionView::Template::LegacyTemplate.new(view, source)) + end + end + # Register an object that knows how to handle template files with the given # extensions. This can be used to implement new template types. # The handler must respond to +:call+, which will be passed the template # and should return the rendered template as a String. def register_template_handler(*extensions, handler) + params = if handler.is_a?(Proc) + handler.parameters + else + handler.method(:call).parameters + end + + unless params.find_all { |type, _| type == :req || type == :opt }.length >= 2 + ActiveSupport::Deprecation.warn <<~eowarn + Single arity template handlers are deprecated. Template handlers must + now accept two parameters, the view object and the source for the view object. + Change: + >> #{handler.class}#call(#{params.map(&:last).join(", ")}) + To: + >> #{handler.class}#call(#{params.map(&:last).join(", ")}, source) + eowarn + handler = LegacyHandlerWrapper.new(handler) + end + raise(ArgumentError, "Extension is required") if extensions.empty? extensions.each do |extension| @@template_handlers[extension.to_sym] = handler diff --git a/actionview/lib/action_view/template/handlers/builder.rb b/actionview/lib/action_view/template/handlers/builder.rb index 61492ce448..e5413cd2a0 100644 --- a/actionview/lib/action_view/template/handlers/builder.rb +++ b/actionview/lib/action_view/template/handlers/builder.rb @@ -5,11 +5,11 @@ module ActionView class Builder class_attribute :default_format, default: :xml - def call(template) + def call(template, source) require_engine "xml = ::Builder::XmlMarkup.new(:indent => 2);" \ "self.output_buffer = xml.target!;" + - template.source + + source + ";xml.target!;" end diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb index 7d1a6767d7..b6314a5bc3 100644 --- a/actionview/lib/action_view/template/handlers/erb.rb +++ b/actionview/lib/action_view/template/handlers/erb.rb @@ -28,8 +28,8 @@ module ActionView ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*") - def self.call(template) - new.call(template) + def self.call(template, source) + new.call(template, source) end def supports_streaming? @@ -40,17 +40,17 @@ module ActionView true end - def call(template) + def call(template, source) # First, convert to BINARY, so in case the encoding is # wrong, we can still find an encoding tag # (<%# encoding %>) inside the String using a regular # expression - template_source = template.source.dup.force_encoding(Encoding::ASCII_8BIT) + template_source = source.dup.force_encoding(Encoding::ASCII_8BIT) erb = template_source.gsub(ENCODING_TAG, "") encoding = $2 - erb.force_encoding valid_encoding(template.source.dup, encoding) + erb.force_encoding valid_encoding(source.dup, encoding) # Always make sure we return a String in the default_internal erb.encode! diff --git a/actionview/lib/action_view/template/handlers/html.rb b/actionview/lib/action_view/template/handlers/html.rb index 27004a318c..65857d8587 100644 --- a/actionview/lib/action_view/template/handlers/html.rb +++ b/actionview/lib/action_view/template/handlers/html.rb @@ -3,7 +3,7 @@ module ActionView module Template::Handlers class Html < Raw - def call(template) + def call(template, source) "ActionView::OutputBuffer.new #{super}" end end diff --git a/actionview/lib/action_view/template/handlers/raw.rb b/actionview/lib/action_view/template/handlers/raw.rb index 5cd23a0060..57ebb169fc 100644 --- a/actionview/lib/action_view/template/handlers/raw.rb +++ b/actionview/lib/action_view/template/handlers/raw.rb @@ -3,8 +3,8 @@ module ActionView module Template::Handlers class Raw - def call(template) - "#{template.source.inspect}.html_safe;" + def call(template, source) + "#{source.inspect}.html_safe;" end end end diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 12ae82f8c5..3b4594942b 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -226,9 +226,8 @@ module ActionView template_paths.map do |template| handler, format, variant = extract_handler_and_format_and_variant(template) - contents = File.binread(template) - Template.new(contents, File.expand_path(template), handler, + FileTemplate.new(File.expand_path(template), handler, virtual_path: path.virtual, format: format, variant: variant, diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb index 56d617309c..acc2ed029b 100644 --- a/actionview/test/abstract_unit.rb +++ b/actionview/test/abstract_unit.rb @@ -58,7 +58,7 @@ module RenderERBUtils template = ActionView::Template.new( string.strip, "test template", - ActionView::Template::Handlers::ERB, + ActionView::Template.handler_for_extension(:erb), {}) template.render(ActionView::Base.empty, {}).strip diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb index 6d5c97b7fd..838b564c5d 100644 --- a/actionview/test/actionpack/controller/layout_test.rb +++ b/actionview/test/actionpack/controller/layout_test.rb @@ -70,7 +70,7 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase end def test_third_party_template_library_auto_discovers_layout - with_template_handler :mab, lambda { |template| template.source.inspect } do + with_template_handler :mab, lambda { |template, source| source.inspect } do @controller = ThirdPartyTemplateLibraryController.new get :hello assert_response :success @@ -212,7 +212,7 @@ class LayoutSetInResponseTest < ActionController::TestCase end def test_layout_set_when_using_render - with_template_handler :mab, lambda { |template| template.source.inspect } do + with_template_handler :mab, lambda { |template, source| source.inspect } do @controller = SetsLayoutInRenderController.new get :hello assert_includes @response.body, "layouts/third_party_template_library.mab" diff --git a/actionview/test/template/dependency_tracker_test.rb b/actionview/test/template/dependency_tracker_test.rb index ef7aeac039..42cb14a18a 100644 --- a/actionview/test/template/dependency_tracker_test.rb +++ b/actionview/test/template/dependency_tracker_test.rb @@ -17,8 +17,8 @@ class FakeTemplate end end -Neckbeard = lambda { |template| template.source } -Bowtie = lambda { |template| template.source } +Neckbeard = lambda { |template, source| source } +Bowtie = lambda { |template, source| source } class DependencyTrackerTest < ActionView::TestCase def tracker diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb index 6db55a1447..ca117d4a30 100644 --- a/actionview/test/template/form_collections_helper_test.rb +++ b/actionview/test/template/form_collections_helper_test.rb @@ -48,8 +48,16 @@ class FormCollectionsHelperTest < ActionView::TestCase 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" + assert_select "label[for=user_name_0_99]", "$0.99" + assert_select "label[for=user_name_1_99]", "$1.99" + end + + test "collection radio correctly builds unique DOM IDs for float values" do + with_collection_radio_buttons :user, :name, [1.0, 10], :to_s, :to_s + assert_select "label[for=user_name_1_0]", "1.0" + assert_select "label[for=user_name_10]", "10" + assert_select 'input#user_name_1_0[type=radio][value="1.0"]' + assert_select 'input#user_name_10[type=radio][value="10"]' end test "collection radio accepts checked item" do @@ -302,8 +310,16 @@ class FormCollectionsHelperTest < ActionView::TestCase 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" + assert_select "label[for=user_name_0_99]", "$0.99" + assert_select "label[for=user_name_1_99]", "$1.99" + end + + test "collection check boxes correctly builds unique DOM IDs for float values" do + with_collection_check_boxes :user, :name, [1.0, 10], :to_s, :to_s + assert_select "label[for=user_name_1_0]", "1.0" + assert_select "label[for=user_name_10]", "10" + assert_select 'input#user_name_1_0[type=checkbox][value="1.0"]' + assert_select 'input#user_name_10[type=checkbox][value="10"]' end test "collection check boxes generates labels for non-English values correctly" do diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index 76ffe3415d..f89d087c34 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -450,13 +450,31 @@ module RenderTestCases assert_equal "Hello, World!", @view.render(inline: "Hello, World!", type: :bar) end - CustomHandler = lambda do |template| + CustomHandler = lambda do |template, source| "@output_buffer = ''.dup\n" \ - "@output_buffer << 'source: #{template.source.inspect}'\n" + "@output_buffer << 'source: #{source.inspect}'\n" end def test_render_inline_with_render_from_to_proc - ActionView::Template.register_template_handler :ruby_handler, :source.to_proc + ActionView::Template.register_template_handler :ruby_handler, lambda { |_, source| source } + assert_equal "3", @view.render(inline: "(1 + 2).to_s", type: :ruby_handler) + ensure + ActionView::Template.unregister_template_handler :ruby_handler + end + + def test_render_inline_with_render_from_to_proc_deprecated + assert_deprecated do + ActionView::Template.register_template_handler :ruby_handler, :source.to_proc + end + assert_equal "3", @view.render(inline: "(1 + 2).to_s", type: :ruby_handler) + ensure + ActionView::Template.unregister_template_handler :ruby_handler + end + + def test_optional_second_arg_works_without_deprecation + assert_not_deprecated do + ActionView::Template.register_template_handler :ruby_handler, ->(view, source = nil) { source } + end assert_equal "3", @view.render(inline: "(1 + 2).to_s", type: :ruby_handler) ensure ActionView::Template.unregister_template_handler :ruby_handler diff --git a/actionview/test/ujs/server.rb b/actionview/test/ujs/server.rb index 56f436c8b8..d7a6271587 100644 --- a/actionview/test/ujs/server.rb +++ b/actionview/test/ujs/server.rb @@ -23,6 +23,7 @@ module UJS config.public_file_server.enabled = true config.logger = Logger.new(STDOUT) config.log_level = :error + config.hosts << proc { true } config.content_security_policy do |policy| policy.default_src :self, :https |