diff options
Diffstat (limited to 'actionview')
98 files changed, 1278 insertions, 870 deletions
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 5e7b271fb9..dcd3e33c46 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,214 +1,3 @@ -* ActionView::Template.finalize_compiled_template_methods is deprecated with - no replacement. - *tenderlove* -* config.action_view.finalize_compiled_template_methods is deprecated with - no replacement. - - *tenderlove* - -* 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 - [`rails-ujs`](https://www.npmjs.com/package/rails-ujs) to - [`@rails/ujs`](https://www.npmjs.com/package/@rails/ujs). - - *Javan Makhmali* - -* Remove deprecated `image_alt` helper. - - *Rafael Mendonça França* - -* Fix the need of `#protect_against_forgery?` method defined in - `ActionView::Base` subclasses. This prevents the use of forms and buttons. - - *Genadi Samokovarov* - -* Fix UJS permanently showing disabled text in a[data-remote][data-disable-with] elements within forms. - - Fixes #33889. - - *Wolfgang Hobmaier* - -* Prevent non-primary mouse keys from triggering Rails UJS click handlers. - Firefox fires click events even if the click was triggered by non-primary mouse keys such as right- or scroll-wheel-clicks. - For example, right-clicking a link such as the one described below (with an underlying ajax request registered on click) should not cause that request to occur. - - ``` - <%= link_to 'Remote', remote_path, class: 'remote', remote: true, data: { type: :json } %> - ``` - - Fixes #34541. - - *Wolfgang Hobmaier* - -* Prevent `ActionView::TextHelper#word_wrap` from unexpectedly stripping white space from the _left_ side of lines. - - For example, given input like this: - - ``` - This is a paragraph with an initial indent, - followed by additional lines that are not indented, - and finally terminated with a blockquote: - "A pithy saying" - ``` - - Calling `word_wrap` should not trim the indents on the first and last lines. - - Fixes #34487. - - *Lyle Mullican* - -* Add allocations to template rendering instrumentation. - - Adds the allocations for template and partial rendering to the server output on render. - - ``` - Rendered posts/_form.html.erb (Duration: 7.1ms | Allocations: 6004) - Rendered posts/new.html.erb within layouts/application (Duration: 8.3ms | Allocations: 6654) - Completed 200 OK in 858ms (Views: 848.4ms | ActiveRecord: 0.4ms | Allocations: 1539564) - ``` - - *Eileen M. Uchitelle*, *Aaron Patterson* - -* Respect the `only_path` option passed to `url_for` when the options are passed in as an array - - Fixes #33237. - - *Joel Ambass* - -* Deprecate calling private model methods from view helpers. - - For example, in methods like `options_from_collection_for_select` - and `collection_select` it is possible to call private methods from - the objects used. - - Fixes #33546. - - *Ana María Martínez Gómez* - -* Fix issue with `button_to`'s `to_form_params` - - `button_to` was throwing exception when invoked with `params` hash that - contains symbol and string keys. The reason for the exception was that - `to_form_params` was comparing the given symbol and string keys. - - The issue is fixed by turning all keys to strings inside - `to_form_params` before comparing them. - - *Georgi Georgiev* - -* Mark arrays of translations as trusted safe by using the `_html` suffix. - - Example: - - en: - foo_html: - - "One" - - "<strong>Two</strong>" - - "Three 👋 🙂" - - *Juan Broullon* - -* Add `year_format` option to date_select tag. This option makes it possible to customize year - names. Lambda should be passed to use this option. - - Example: - - date_select('user_birthday', '', start_year: 1998, end_year: 2000, year_format: ->year { "Heisei #{year - 1988}" }) - - The HTML produced: - - <select id="user_birthday__1i" name="user_birthday[(1i)]"> - <option value="1998">Heisei 10</option> - <option value="1999">Heisei 11</option> - <option value="2000">Heisei 12</option> - </select> - /* The rest is omitted */ - - *Koki Ryu* - -* Fix JavaScript views rendering does not work with Firefox when using - Content Security Policy. - - Fixes #32577. - - *Yuji Yaginuma* - -* Add the `nonce: true` option for `javascript_include_tag` helper to - support automatic nonce generation for Content Security Policy. - Works the same way as `javascript_tag nonce: true` does. - - *Yaroslav Markin* - -* Remove `ActionView::Helpers::RecordTagHelper`. - - *Yoshiyuki Hirano* - -* Disable `ActionView::Template` finalizers in test environment. - - Template finalization can be expensive in large view test suites. - Add a configuration option, - `action_view.finalize_compiled_template_methods`, and turn it off in - the test environment. - - *Simon Coffey* - -* Extract the `confirm` call in its own, overridable method in `rails_ujs`. - - Example: - - Rails.confirm = function(message, element) { - return (my_bootstrap_modal_confirm(message)); - } - - *Mathieu Mahé* - -* Enable select tag helper to mark `prompt` option as `selected` and/or `disabled` for `required` - field. - - Example: - - select :post, - :category, - ["lifestyle", "programming", "spiritual"], - { selected: "", disabled: "", prompt: "Choose one" }, - { required: true } - - Placeholder option would be selected and disabled. - - The HTML produced: - - <select required="required" name="post[category]" id="post_category"> - <option disabled="disabled" selected="selected" value="">Choose one</option> - <option value="lifestyle">lifestyle</option> - <option value="programming">programming</option> - <option value="spiritual">spiritual</option></select> - - *Sergey Prikhodko* - -* Don't enforce UTF-8 by default. - - With the disabling of TLS 1.0 by most major websites, continuing to run - IE8 or lower becomes increasingly difficult so default to not enforcing - UTF-8 encoding as it's not relevant to other browsers. - - *Andrew White* - -* Change translation key of `submit_tag` from `module_name_class_name` to `module_name/class_name`. - - *Rui Onodera* - -* Rails 6 requires Ruby 2.5.0 or newer. - - *Jeremy Daer*, *Kasper Timm Hansen* - - -Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/actionview/CHANGELOG.md) for previous changes. +Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/actionview/CHANGELOG.md) for previous changes. diff --git a/actionview/README.rdoc b/actionview/README.rdoc index 03a0723564..ba50c67024 100644 --- a/actionview/README.rdoc +++ b/actionview/README.rdoc @@ -5,6 +5,8 @@ view helpers that assist when building HTML forms, Atom feeds and more. Template formats that Action View handles are ERB (embedded Ruby, typically used to inline short Ruby snippets inside HTML), and XML Builder. +You can read more about Action View in the {Action View Overview}[https://edgeguides.rubyonrails.org/action_view_overview.html] guide. + == Download and installation The latest version of Action View can be installed with RubyGems: @@ -27,7 +29,7 @@ Action View is released under the MIT license: API documentation is at -* http://api.rubyonrails.org +* https://api.rubyonrails.org Bug reports for the Ruby on Rails project can be filed here: diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec index d8bd233ceb..200c775dcc 100644 --- a/actionview/actionview.gemspec +++ b/actionview/actionview.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |s| s.author = "David Heinemeier Hansson" s.email = "david@loudthinking.com" - s.homepage = "http://rubyonrails.org" + s.homepage = "https://rubyonrails.org" s.files = Dir["CHANGELOG.md", "README.rdoc", "MIT-LICENSE", "lib/**/*"] s.require_path = "lib" diff --git a/actionview/app/assets/javascripts/README.md b/actionview/app/assets/javascripts/README.md index b9682b61e2..aa167004b6 100644 --- a/actionview/app/assets/javascripts/README.md +++ b/actionview/app/assets/javascripts/README.md @@ -52,5 +52,5 @@ Run `bundle exec rake ujs:server` first, and then run the web tests by visiting rails-ujs is released under the [MIT License](MIT-LICENSE). [data]: https://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data-with-the-data-attributes "Embedding custom non-visible data with the data-* attributes" -[validator]: http://validator.w3.org/ -[csrf]: http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html +[validator]: https://validator.w3.org/ +[csrf]: https://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html diff --git a/actionview/app/assets/javascripts/rails-ujs/start.coffee b/actionview/app/assets/javascripts/rails-ujs/start.coffee index 5c1214df59..0347058195 100644 --- a/actionview/app/assets/javascripts/rails-ujs/start.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/start.coffee @@ -2,6 +2,7 @@ fire, delegate getData, $ refreshCSRFTokens, CSRFProtection + loadCSPNonce enableElement, disableElement, handleDisabledElement handleConfirm, preventInsignificantClick handleRemote, formSubmitButtonClick, @@ -67,6 +68,7 @@ Rails.start = -> delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick document.addEventListener('DOMContentLoaded', refreshCSRFTokens) + document.addEventListener('DOMContentLoaded', loadCSPNonce) window._rails_loaded = true if window.Rails is Rails and fire(document, 'rails:attachBindings') diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee index 019bda635a..5b223d50f6 100644 --- a/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee @@ -69,7 +69,7 @@ processResponse = (response, type) -> script.setAttribute('nonce', cspNonce()) script.text = response document.head.appendChild(script).parentNode.removeChild(script) - else if type.match(/\bxml\b/) + else if type.match(/\b(xml|html|svg)\b/) parser = new DOMParser() type = type.replace(/;.+/, '') # remove something like ';charset=utf-8' try response = parser.parseFromString(response, type) diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee index 8d2d6ce447..a33f531375 100644 --- a/actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee @@ -1,4 +1,8 @@ -# Content-Security-Policy nonce for inline scripts -cspNonce = Rails.cspNonce = -> - meta = document.querySelector('meta[name=csp-nonce]') - meta and meta.content +nonce = null + +Rails.loadCSPNonce = -> + nonce = document.querySelector("meta[name=csp-nonce]")?.content + +# Returns the Content-Security-Policy nonce for inline scripts. +Rails.cspNonce = -> + nonce ? Rails.loadCSPNonce() diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee index a7eee52060..768d9683d4 100644 --- a/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee @@ -27,7 +27,7 @@ if typeof CustomEvent isnt 'function' # obj:: # a native DOM element # name:: -# string that corrspends to the event you want to trigger +# string that corresponds to the event you want to trigger # e.g. 'click', 'submit' # data:: # data you want to pass when you dispatch an event diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb index 8cb4648a67..7f85bf2a5e 100644 --- a/actionview/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -44,7 +44,7 @@ module ActionView autoload :Rendering autoload :RoutingUrlFor autoload :Template - autoload :FileTemplate + autoload :UnboundTemplate autoload :ViewPaths autoload_under "renderer" do @@ -81,6 +81,7 @@ module ActionView end end + autoload :CacheExpiry autoload :TestCase def self.eager_load! diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb index 420136d6de..5253ef7b0c 100644 --- a/actionview/lib/action_view/base.rb +++ b/actionview/lib/action_view/base.rb @@ -196,10 +196,9 @@ module ActionView #:nodoc: end end - attr_reader :view_renderer + attr_reader :view_renderer, :lookup_context attr_internal :config, :assigns - delegate :lookup_context, to: :view_renderer delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, to: :lookup_context def assign(new_assigns) # :nodoc: @@ -208,12 +207,19 @@ module ActionView #:nodoc: # :stopdoc: - def self.build_renderer(context, controller, formats) - lookup_context = context.is_a?(ActionView::LookupContext) ? - context : ActionView::LookupContext.new(context) - lookup_context.formats = formats if formats - lookup_context.prefixes = controller._prefixes if controller - ActionView::Renderer.new(lookup_context) + def self.build_lookup_context(context) + case context + when ActionView::Renderer + context.lookup_context + when Array + ActionView::LookupContext.new(context) + when ActionView::PathSet + ActionView::LookupContext.new(context) + when nil + ActionView::LookupContext.new([]) + else + raise NotImplementedError, context.class.name + end end def self.empty @@ -225,54 +231,80 @@ module ActionView #:nodoc: end def self.with_context(context, assigns = {}, controller = nil) - new ActionView::Renderer.new(context), assigns, controller + new context, assigns, controller end NULL = Object.new # :startdoc: - def initialize(renderer = nil, assigns = {}, controller = nil, formats = NULL) #:nodoc: + def initialize(lookup_context = nil, assigns = {}, controller = nil, formats = NULL) #:nodoc: @_config = ActiveSupport::InheritableOptions.new - if formats == NULL - formats = nil - else - ActiveSupport::Deprecation.warn <<~eowarn + unless formats == NULL + ActiveSupport::Deprecation.warn <<~eowarn.squish Passing formats to ActionView::Base.new is deprecated eowarn end - if renderer.is_a?(ActionView::Renderer) - @view_renderer = renderer + case lookup_context + when ActionView::LookupContext + @lookup_context = lookup_context else - ActiveSupport::Deprecation.warn <<~eowarn - ActionView::Base instances should be constructed with a view renderer, - assigments, and a controller. + ActiveSupport::Deprecation.warn <<~eowarn.squish + ActionView::Base instances should be constructed with a lookup context, + assignments, and a controller. eowarn - @view_renderer = self.class.build_renderer(renderer, controller, formats) + @lookup_context = self.class.build_lookup_context(lookup_context) end + @view_renderer = ActionView::Renderer.new @lookup_context + @current_template = nil + @cache_hit = {} assign(assigns) assign_controller(controller) _prepare_context end - def run(method, locals, buffer, &block) - _old_output_buffer, _old_virtual_path = @output_buffer, @virtual_path + def _run(method, template, locals, buffer, &block) + _old_output_buffer, _old_virtual_path, _old_template = @output_buffer, @virtual_path, @current_template + @current_template = template @output_buffer = buffer send(method, locals, buffer, &block) ensure - @output_buffer, @virtual_path = _old_output_buffer, _old_virtual_path + @output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template end def compiled_method_container - raise NotImplementedError, <<~msg - Subclasses of ActionView::Base must implement `compiled_method_container` - or use the class method `with_empty_template_cache` for constructing - an ActionView::Base subclass that has an empty cache. - msg + if self.class == ActionView::Base + ActiveSupport::Deprecation.warn <<~eowarn.squish + ActionView::Base instances must implement `compiled_method_container` + or use the class method `with_empty_template_cache` for constructing + an ActionView::Base instances that has an empty cache. + eowarn + end + + self.class + end + + def in_rendering_context(options) + old_view_renderer = @view_renderer + old_lookup_context = @lookup_context + + if !lookup_context.html_fallback_for_js && options[:formats] + formats = Array(options[:formats]) + if formats == [:js] + formats << :html + end + @lookup_context = lookup_context.with_prepended_formats(formats) + @view_renderer = ActionView::Renderer.new @lookup_context + end + + yield @view_renderer + ensure + @view_renderer = old_view_renderer + @lookup_context = old_lookup_context end ActiveSupport.run_load_hooks(:action_view, self) diff --git a/actionview/lib/action_view/cache_expiry.rb b/actionview/lib/action_view/cache_expiry.rb new file mode 100644 index 0000000000..820afc093d --- /dev/null +++ b/actionview/lib/action_view/cache_expiry.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module ActionView + class CacheExpiry + class Executor + def initialize(watcher:) + @cache_expiry = CacheExpiry.new(watcher: watcher) + end + + def before(target) + @cache_expiry.clear_cache_if_necessary + end + end + + def initialize(watcher:) + @watched_dirs = nil + @watcher_class = watcher + @watcher = nil + end + + def clear_cache_if_necessary + watched_dirs = dirs_to_watch + if watched_dirs != @watched_dirs + @watched_dirs = watched_dirs + @watcher = @watcher_class.new([], watched_dirs) do + clear_cache + end + @watcher.execute + else + @watcher.execute_if_updated + end + end + + def clear_cache + ActionView::LookupContext::DetailsKey.clear + end + + private + + def dirs_to_watch + fs_paths = all_view_paths.grep(FileSystemResolver) + fs_paths.map(&:path).sort.uniq + end + + def all_view_paths + ActionView::ViewPaths.all_view_paths.flat_map(&:paths) + end + end +end diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 6d2e471a44..97a6da3634 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -6,23 +6,17 @@ module ActionView class Digestor @@digest_mutex = Mutex.new - module PerExecutionDigestCacheExpiry - def self.before(target) - ActionView::LookupContext::DetailsKey.clear - end - end - class << self # Supported options: # # * <tt>name</tt> - Template name # * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt> # * <tt>dependencies</tt> - An array of dependent views - def digest(name:, finder:, dependencies: nil) + def digest(name:, format:, finder:, dependencies: nil) if dependencies.nil? || dependencies.empty? - cache_key = "#{name}.#{finder.rendered_format}" + cache_key = "#{name}.#{format}" else - cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join(".") + cache_key = [ name, format, dependencies ].flatten.compact.join(".") end # this is a correctly done double-checked locking idiom @@ -48,8 +42,6 @@ module ActionView logical_name = name.gsub(%r|/_|, "/") if template = find_template(finder, logical_name, [], partial, []) - finder.rendered_format ||= template.formats.first - if node = seen[template.identifier] # handle cycles in the tree node else @@ -73,9 +65,7 @@ module ActionView private def find_template(finder, name, prefixes, partial, keys) finder.disable_cache do - format = finder.rendered_format - result = finder.find_all(name, prefixes, partial, keys, formats: [format]).first if format - result || finder.find_all(name, prefixes, partial, keys).first + finder.find_all(name, prefixes, partial, keys).first end end end diff --git a/actionview/lib/action_view/file_template.rb b/actionview/lib/action_view/file_template.rb deleted file mode 100644 index 4921074383..0000000000 --- a/actionview/lib/action_view/file_template.rb +++ /dev/null @@ -1,33 +0,0 @@ -# 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/gem_version.rb b/actionview/lib/action_view/gem_version.rb index 02de3eeec2..011662d8d1 100644 --- a/actionview/lib/action_view/gem_version.rb +++ b/actionview/lib/action_view/gem_version.rb @@ -8,9 +8,9 @@ module ActionView module VERSION MAJOR = 6 - MINOR = 0 + MINOR = 1 TINY = 0 - PRE = "beta1" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index b1a14250c3..020aebeea3 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -216,13 +216,13 @@ module ActionView end end - def digest_path_from_virtual(virtual_path) # :nodoc: - digest = Digestor.digest(name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies) + def digest_path_from_template(template) # :nodoc: + digest = Digestor.digest(name: template.virtual_path, format: template.format, finder: lookup_context, dependencies: view_cache_dependencies) if digest.present? - "#{virtual_path}:#{digest}" + "#{template.virtual_path}:#{digest}" else - virtual_path + template.virtual_path end end @@ -234,7 +234,7 @@ module ActionView if virtual_path || digest_path name = controller.url_for(name).split("://").last if name.is_a?(Hash) - digest_path ||= digest_path_from_virtual(virtual_path) + digest_path ||= digest_path_from_template(@current_template) [ digest_path, name ] else diff --git a/actionview/lib/action_view/helpers/csp_helper.rb b/actionview/lib/action_view/helpers/csp_helper.rb index e2e065c218..4415018845 100644 --- a/actionview/lib/action_view/helpers/csp_helper.rb +++ b/actionview/lib/action_view/helpers/csp_helper.rb @@ -14,9 +14,11 @@ module ActionView # This is used by the Rails UJS helper to create dynamically # loaded inline <script> elements. # - def csp_meta_tag + def csp_meta_tag(**options) if content_security_policy? - tag("meta", name: "csp-nonce", content: content_security_policy_nonce) + options[:name] = "csp-nonce" + options[:content] = content_security_policy_nonce + tag("meta", options) end end end diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index c5a736bfb4..5533cef249 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -739,7 +739,7 @@ module ActionView # def labelled_form_with(**options, &block) # form_with(**options.merge(builder: LabellingFormBuilder), &block) # end - def form_with(model: nil, scope: nil, url: nil, format: nil, **options) + def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block) options[:allow_method_names_outside_object] = true options[:skip_default_ids] = !form_with_generates_ids @@ -752,7 +752,7 @@ module ActionView if block_given? builder = instantiate_builder(scope, model, options) - output = capture(builder, &Proc.new) + output = capture(builder, &block) options[:multipart] ||= builder.multipart? html_options = html_options_for_form_with(url, model, options) diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index c0996049f0..5d4ff36425 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -24,7 +24,7 @@ module ActionView mattr_accessor :default_enforce_utf8, default: true - # Starts a form tag that points the action to a url configured with <tt>url_for_options</tt> just like + # Starts a form tag that points the action to a URL configured with <tt>url_for_options</tt> just like # ActionController::Base#url_for. The method for the form defaults to POST. # # ==== Options diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb index 279cde5e76..52a951b2ca 100644 --- a/actionview/lib/action_view/helpers/output_safety_helper.rb +++ b/actionview/lib/action_view/helpers/output_safety_helper.rb @@ -38,7 +38,7 @@ module ActionView #:nodoc: # Converts the array to a comma-separated sentence where the last element is # joined by the connector word. This is the html_safe-aware version of - # ActiveSupport's {Array#to_sentence}[http://api.rubyonrails.org/classes/Array.html#method-i-to_sentence]. + # ActiveSupport's {Array#to_sentence}[https://api.rubyonrails.org/classes/Array.html#method-i-to_sentence]. # def to_sentence(array, options = {}) options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb index 1e12aa2736..7ead691113 100644 --- a/actionview/lib/action_view/helpers/rendering_helper.rb +++ b/actionview/lib/action_view/helpers/rendering_helper.rb @@ -27,10 +27,12 @@ module ActionView def render(options = {}, locals = {}, &block) case options when Hash - if block_given? - view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block) - else - view_renderer.render(self, options) + in_rendering_context(options) do |renderer| + if block_given? + view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block) + else + view_renderer.render(self, options) + end end else view_renderer.render_partial(self, partial: options, locals: locals, &block) diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index 0adecf362a..39c01f3334 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -106,7 +106,7 @@ module ActionView end def tag_name(multiple = false, index = nil) - # a little duplication to construct less strings + # a little duplication to construct fewer strings case when @object_name.empty? "#{sanitized_method_name}#{multiple ? "[]" : ""}" @@ -118,7 +118,7 @@ module ActionView end def tag_id(index = nil) - # a little duplication to construct less strings + # a little duplication to construct fewer strings case when @object_name.empty? sanitized_method_name.dup @@ -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:]]]/, "").downcase 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 67c86592b9..d5b0a9263f 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -114,7 +114,7 @@ module ActionView # Delegates to <tt>I18n.localize</tt> with no additional functionality. # - # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize + # See https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize # for more information. def localize(*args) I18n.localize(*args) @@ -138,7 +138,7 @@ module ActionView end def html_safe_translation_key?(key) - /([_.]|\b)html\z/.match?(key.to_s) + /(?:_|\b)html\z/.match?(key.to_s) end end end diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index d63ada3890..df83dff681 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -553,7 +553,7 @@ module ActionView url_string = URI.parser.unescape(url_for(options)).force_encoding(Encoding::BINARY) # We ignore any extra parameters in the request_uri if the - # submitted url doesn't have any either. This lets the function + # submitted URL doesn't have any either. This lets the function # work with things like ?order=asc # the behaviour can be disabled with check_parameters: true request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb index 3e6d352c15..08f66bf435 100644 --- a/actionview/lib/action_view/layouts.rb +++ b/actionview/lib/action_view/layouts.rb @@ -322,7 +322,7 @@ module ActionView end class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def _layout(formats) + def _layout(lookup_context, formats) if _conditional_layout? #{layout_definition} else @@ -388,8 +388,8 @@ module ActionView case name when String then _normalize_layout(name) when Proc then name - when true then Proc.new { |formats| _default_layout(formats, true) } - when :default then Proc.new { |formats| _default_layout(formats, false) } + when true then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, true) } + when :default then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, false) } when false, nil then nil else raise ArgumentError, @@ -411,9 +411,9 @@ module ActionView # # ==== Returns # * <tt>template</tt> - The template object for the default layout (or +nil+) - def _default_layout(formats, require_layout = false) + def _default_layout(lookup_context, formats, require_layout = false) begin - value = _layout(formats) if action_has_layout? + value = _layout(lookup_context, formats) if action_has_layout? rescue NameError => e raise e, "Could not render layout: #{e.message}" end diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index 61fe11cf45..b9a8cc129c 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -16,6 +16,8 @@ module ActionView # only once during the request, it speeds up all cache accesses. class LookupContext #:nodoc: attr_accessor :prefixes, :rendered_format + deprecate :rendered_format + deprecate :rendered_format= mattr_accessor :fallbacks, default: FallbackFileSystemResolver.instances @@ -128,9 +130,8 @@ module ActionView end alias :find_template :find - def find_file(name, prefixes = [], partial = false, keys = [], options = {}) - @view_paths.find_file(*args_for_lookup(name, prefixes, partial, keys, options)) - end + alias :find_file :find + deprecate :find_file def find_all(name, prefixes = [], partial = false, keys = [], options = {}) @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options)) @@ -152,7 +153,7 @@ module ActionView view_paths = build_view_paths((@view_paths.paths + self.class.fallbacks).uniq) if block_given? - ActiveSupport::Deprecation.warn <<~eowarn + ActiveSupport::Deprecation.warn <<~eowarn.squish Calling `with_fallbacks` with a block is deprecated. Call methods on the lookup context returned by `with_fallbacks` instead. eowarn @@ -250,7 +251,6 @@ module ActionView @digest_cache = nil @cache = true @prefixes = prefixes - @rendered_format = nil @details = initialize_details({}, details) @view_paths = build_view_paths(view_paths) @@ -260,6 +260,13 @@ module ActionView @digest_cache ||= DetailsKey.digest_cache(@details) end + def with_prepended_formats(formats) + details = @details.dup + details[:formats] = formats + + self.class.new(@view_paths, details, @prefixes) + end + def initialize_details(target, details) registered_details.each do |k| target[k] = details[k] || Accessors::DEFAULT_PROCS[k].call @@ -272,7 +279,15 @@ module ActionView # add :html as fallback to :js. def formats=(values) if values + values = values.dup values.concat(default_formats) if values.delete "*/*" + values.uniq! + + invalid_values = (values - Template::Types.symbols) + unless invalid_values.empty? + raise ArgumentError, "Invalid formats: #{invalid_values.map(&:inspect).join(", ")}" + end + if values == [:js] values << :html @html_fallback_for_js = true diff --git a/actionview/lib/action_view/path_set.rb b/actionview/lib/action_view/path_set.rb index 691b53e2da..d54749d8e3 100644 --- a/actionview/lib/action_view/path_set.rb +++ b/actionview/lib/action_view/path_set.rb @@ -48,12 +48,11 @@ module ActionView #:nodoc: find_all(*args).first || raise(MissingTemplate.new(self, *args)) end - def find_file(path, prefixes = [], *args) - _find_all(path, prefixes, args, true).first || raise(MissingTemplate.new(self, path, prefixes, *args)) - end + alias :find_file :find + deprecate :find_file def find_all(path, prefixes = [], *args) - _find_all path, prefixes, args, false + _find_all path, prefixes, args end def exists?(path, prefixes, *args) @@ -71,15 +70,11 @@ module ActionView #:nodoc: private - def _find_all(path, prefixes, args, outside_app) + def _find_all(path, prefixes, args) prefixes = [prefixes] if String === prefixes prefixes.each do |prefix| paths.each do |resolver| - if outside_app - templates = resolver.find_all_anywhere(path, prefix, *args) - else - templates = resolver.find_all(path, prefix, *args) - end + templates = resolver.find_all(path, prefix, *args) return templates unless templates.empty? end end diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb index a25e1d3d02..8bd526cdf9 100644 --- a/actionview/lib/action_view/railtie.rb +++ b/actionview/lib/action_view/railtie.rb @@ -81,7 +81,7 @@ module ActionView initializer "action_view.per_request_digest_cache" do |app| ActiveSupport.on_load(:action_view) do unless ActionView::Resolver.caching? - app.executor.to_run ActionView::Digestor::PerExecutionDigestCacheExpiry + app.executor.to_run ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher) end end end diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb index ae366ce54a..b397c02cde 100644 --- a/actionview/lib/action_view/renderer/abstract_renderer.rb +++ b/actionview/lib/action_view/renderer/abstract_renderer.rb @@ -27,6 +27,52 @@ module ActionView raise NotImplementedError end + class RenderedCollection # :nodoc: + def self.empty(format) + EmptyCollection.new format + end + + attr_reader :rendered_templates + + def initialize(rendered_templates, spacer) + @rendered_templates = rendered_templates + @spacer = spacer + end + + def body + @rendered_templates.map(&:body).join(@spacer.body).html_safe + end + + def format + rendered_templates.first.format + end + + class EmptyCollection + attr_reader :format + + def initialize(format) + @format = format + end + + def body; nil; end + end + end + + class RenderedTemplate # :nodoc: + attr_reader :body, :template + + def initialize(body, template) + @body = body + @template = template + end + + def format + template.format + end + + EMPTY_SPACER = Struct.new(:body).new + end + private def extract_details(options) # :doc: @@ -49,5 +95,13 @@ module ActionView @lookup_context.formats = formats | @lookup_context.formats end + + def build_rendered_template(content, template) + RenderedTemplate.new content, template + end + + def build_rendered_collection(templates, spacer) + RenderedCollection.new templates, spacer + end end end diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index 801916954f..608e417583 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -305,18 +305,13 @@ module ActionView else @template_keys = @locals.keys end - template = find_partial(@path, @template_keys) + template = find_template(@path, @template_keys) @variable ||= template.variable else - template = nil - end - - @lookup_context.rendered_format ||= begin - if template && template.formats.first - template.formats.first - else - formats.first + if options[:cached] + raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering" end + template = nil end if @collection @@ -331,15 +326,23 @@ module ActionView def render_collection(view, template) identifier = (template && template.identifier) || @path instrument(:collection, identifier: identifier, count: @collection.size) do |payload| - return nil if @collection.blank? + return RenderedCollection.empty(@lookup_context.formats.first) if @collection.blank? - if @options.key?(:spacer_template) - spacer = find_template(@options[:spacer_template], @locals.keys).render(view, @locals) + spacer = if @options.key?(:spacer_template) + spacer_template = find_template(@options[:spacer_template], @locals.keys) + build_rendered_template(spacer_template.render(view, @locals), spacer_template) + else + RenderedTemplate::EMPTY_SPACER end - cache_collection_render(payload, view, template) do - template ? collection_with_template(view, template) : collection_without_template(view) - end.join(spacer).html_safe + collection_body = if template + cache_collection_render(payload, view, template) do + collection_with_template(view, template) + end + else + collection_without_template(view) + end + build_rendered_collection(collection_body, spacer) end end @@ -361,7 +364,7 @@ module ActionView content = layout.render(view, locals) { content } if layout payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path] - content + build_rendered_template(content, template) end end @@ -379,8 +382,6 @@ module ActionView @locals = options[:locals] || {} @details = extract_details(options) - prepend_formats(options[:formats]) - partial = options[:partial] if String === partial @@ -427,10 +428,6 @@ module ActionView @object.to_ary if @object.respond_to?(:to_ary) end - def find_partial(path, template_keys) - find_template(path, template_keys) - end - def find_template(path, locals) prefixes = path.include?(?/) ? [] : @lookup_context.prefixes @lookup_context.find_template(path, prefixes, true, locals, @details) @@ -454,7 +451,7 @@ module ActionView content = template.render(view, locals) content = layout.render(view, locals) { content } if layout partial_iteration.iterate! - content + build_rendered_template(content, template) end end @@ -476,7 +473,7 @@ module ActionView template = (cache[path] ||= find_template(path, keys + [as, counter, iteration])) content = template.render(view, locals) partial_iteration.iterate! - content + build_rendered_template(content, template) end end diff --git a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb index 388f9e5e56..ca692699a6 100644 --- a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb +++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb @@ -17,13 +17,13 @@ module ActionView # Result is a hash with the key represents the # key used for cache lookup and the value is the item # on which the partial is being rendered - keyed_collection = collection_by_cache_keys(view, template) + keyed_collection, ordered_keys = collection_by_cache_keys(view, template) # Pull all partials from cache # Result is a hash, key matches the entry in # `keyed_collection` where the cache was retrieved and the # value is the value that was present in the cache - cached_partials = collection_cache.read_multi(*keyed_collection.keys) + cached_partials = collection_cache.read_multi(*keyed_collection.keys) instrumentation_payload[:cache_hits] = cached_partials.size # Extract the items for the keys that are not found @@ -40,11 +40,15 @@ module ActionView rendered_partials = @collection.empty? ? [] : yield index = 0 - fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do + keyed_partials = fetch_or_cache_partial(cached_partials, template, order_by: keyed_collection.each_key) do # This block is called once # for every cache miss while preserving order. rendered_partials[index].tap { index += 1 } end + + ordered_keys.map do |key| + keyed_partials[key] + end end def callable_cache_key? @@ -54,10 +58,12 @@ module ActionView def collection_by_cache_keys(view, template) seed = callable_cache_key? ? @options[:cached] : ->(i) { i } - digest_path = view.digest_path_from_virtual(template.virtual_path) + digest_path = view.digest_path_from_template(template) - @collection.each_with_object({}) do |item, hash| - hash[expanded_cache_key(seed.call(item), view, template, digest_path)] = item + @collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)| + key = expanded_cache_key(seed.call(item), view, template, digest_path) + ordered_keys << key + hash[key] = item end end @@ -81,14 +87,17 @@ module ActionView # # If the partial is not already cached it will also be # written back to the underlying cache store. - def fetch_or_cache_partial(cached_partials, order_by:) - order_by.map do |cache_key| - cached_partials.fetch(cache_key) do - yield.tap do |rendered_partial| - collection_cache.write(cache_key, rendered_partial) - end + def fetch_or_cache_partial(cached_partials, template, order_by:) + order_by.each_with_object({}) do |cache_key, hash| + hash[cache_key] = + if content = cached_partials[cache_key] + build_rendered_template(content, template) + else + yield.tap do |rendered_partial| + collection_cache.write(cache_key, rendered_partial.body) + end + end end - end end end end diff --git a/actionview/lib/action_view/renderer/renderer.rb b/actionview/lib/action_view/renderer/renderer.rb index 3f3a97529d..485eb1a5b4 100644 --- a/actionview/lib/action_view/renderer/renderer.rb +++ b/actionview/lib/action_view/renderer/renderer.rb @@ -19,10 +19,14 @@ module ActionView # Main render entry point shared by Action View and Action Controller. def render(context, options) + render_to_object(context, options).body + end + + def render_to_object(context, options) # :nodoc: if options.key?(:partial) - render_partial(context, options) + render_partial_to_object(context, options) else - render_template(context, options) + render_template_to_object(context, options) end end @@ -41,16 +45,24 @@ module ActionView # Direct access to template rendering. def render_template(context, options) #:nodoc: - TemplateRenderer.new(@lookup_context).render(context, options) + render_template_to_object(context, options).body end # Direct access to partial rendering. def render_partial(context, options, &block) #:nodoc: - PartialRenderer.new(@lookup_context).render(context, options, block) + render_partial_to_object(context, options, &block).body end def cache_hits # :nodoc: @cache_hits ||= {} end + + def render_template_to_object(context, options) #:nodoc: + TemplateRenderer.new(@lookup_context).render(context, options) + end + + def render_partial_to_object(context, options, &block) #:nodoc: + PartialRenderer.new(@lookup_context).render(context, options, block) + end end end diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb index f414620923..19fa399e2c 100644 --- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb @@ -34,7 +34,7 @@ module ActionView return unless logger message = +"\n#{exception.class} (#{exception.message}):\n" - message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) + message << exception.annotated_source_code.to_s if exception.respond_to?(:annotated_source_code) message << " " << exception.backtrace.join("\n ") logger.fatal("#{message}\n\n") end @@ -44,7 +44,7 @@ module ActionView # object that responds to each. This object is initialized with a block # that knows how to render the template. def render_template(view, template, layout_name = nil, locals = {}) #:nodoc: - return [super] unless layout_name && template.supports_streaming? + return [super.body] unless layout_name && template.supports_streaming? locals ||= {} layout = layout_name && find_layout(layout_name, locals.keys, [formats.first]) diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index c36baeffcd..4cd8fc5bb2 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -8,9 +8,7 @@ module ActionView @details = extract_details(options) template = determine_template(options) - prepend_formats(template.formats) - - @lookup_context.rendered_format ||= (template.formats.first || formats.first) + prepend_formats(template.format) render_template(context, template, options[:layout], options[:locals] || {}) end @@ -28,10 +26,20 @@ module ActionView elsif options.key?(:html) Template::HTML.new(options[:html], formats.first) elsif options.key?(:file) - @lookup_context.with_fallbacks.find_file(options[:file], nil, false, keys, @details) + if File.exist?(options[:file]) + Template::RawFile.new(options[:file]) + else + ActiveSupport::Deprecation.warn "render file: should be given the absolute path to a file" + @lookup_context.with_fallbacks.find_template(options[:file], nil, false, keys, @details) + end elsif options.key?(:inline) handler = Template.handler_for_extension(options[:type] || "erb") - Template.new(options[:inline], "inline template", handler, locals: keys) + format = if handler.respond_to?(:default_format) + handler.default_format + else + @lookup_context.formats.first + end + Template::Inline.new(options[:inline], "inline template", handler, locals: keys, format: format) elsif options.key?(:template) if options[:template].respond_to?(:render) options[:template] @@ -46,23 +54,24 @@ module ActionView # Renders the given template. A string representing the layout can be # supplied as well. def render_template(view, template, layout_name, locals) - render_with_layout(view, layout_name, locals) do |layout| + render_with_layout(view, template, layout_name, locals) do |layout| instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do template.render(view, locals) { |*name| view._layout_for(*name) } end end end - def render_with_layout(view, path, locals) + def render_with_layout(view, template, path, locals) layout = path && find_layout(path, locals.keys, [formats.first]) content = yield(layout) - if layout + body = if layout view.view_flow.set(:layout, content) layout.render(view, locals) { |*name| view._layout_for(*name) } else content end + build_rendered_template(body, template) end # This is the method which actually finds the layout using details in the lookup @@ -80,6 +89,7 @@ module ActionView when String begin if layout.start_with?("/") + ActiveSupport::Deprecation.warn "Rendering layouts from an absolute path is deprecated." @lookup_context.with_fallbacks.find_template(layout, nil, false, [], details) else @lookup_context.find_template(layout, nil, false, [], details) @@ -89,7 +99,7 @@ module ActionView raise unless template_exists?(layout, nil, false, [], all_details) end when Proc - resolve_layout(layout.call(formats), keys, formats) + resolve_layout(layout.call(@lookup_context, formats), keys, formats) else layout end diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index da92ce1f5e..5a06bd9da6 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -26,6 +26,13 @@ module ActionView extend ActiveSupport::Concern include ActionView::ViewPaths + attr_reader :rendered_format + + def initialize + @rendered_format = nil + super + end + # Overwrite process to setup I18n proxy. def process(*) #:nodoc: old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context) @@ -82,7 +89,7 @@ module ActionView # # Override this method in a module to change the default behavior. def view_context - view_context_class.new(view_renderer, view_assigns, self) + view_context_class.new(lookup_context, view_assigns, self) end # Returns an object that is able to render templates. @@ -96,10 +103,6 @@ module ActionView _render_template(options) end - def rendered_format - Template::Types[lookup_context.rendered_format] - end - private # Find and render a template based on the options given. @@ -109,17 +112,22 @@ module ActionView context = view_context context.assign assigns if assigns - lookup_context.rendered_format = nil if options[:formats] lookup_context.variants = variant if variant - view_renderer.render(context, options) + rendered_template = context.in_rendering_context(options) do |renderer| + renderer.render_to_object(context, options) + end + + rendered_format = rendered_template.format || lookup_context.formats.first + @rendered_format = Template::Types[rendered_format] + + rendered_template.body end # Assign the rendered format to look up context. def _process_format(format) super - lookup_context.formats = [format.to_sym] - lookup_context.rendered_format = lookup_context.formats.first + lookup_context.formats = [format.to_sym] if format.to_sym end # Normalize args by converting render "foo" to render :action => "foo" and diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index 37f476e554..b80bf56c1b 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -113,43 +113,60 @@ module ActionView eager_autoload do autoload :Error + autoload :RawFile autoload :Handlers autoload :HTML + autoload :Inline + autoload :Sources autoload :Text autoload :Types end extend Template::Handlers - attr_accessor :locals, :formats, :variants, :virtual_path + attr_reader :identifier, :handler, :original_encoding, :updated_at + attr_reader :variable, :format, :variant, :locals, :virtual_path - attr_reader :source, :identifier, :handler, :original_encoding, :updated_at - - attr_reader :variable - - def initialize(source, identifier, handler, details) - format = details[:format] || (handler.default_format if handler.respond_to?(:default_format)) + def initialize(source, identifier, handler, format: nil, variant: nil, locals: nil, virtual_path: nil, updated_at: nil) + unless locals + ActiveSupport::Deprecation.warn "ActionView::Template#initialize requires a locals parameter" + locals = [] + end @source = source @identifier = identifier @handler = handler @compiled = false - @original_encoding = nil - @locals = details[:locals] || [] - @virtual_path = details[:virtual_path] + @locals = locals + @virtual_path = virtual_path @variable = if @virtual_path - base = @virtual_path[-1] == "/" ? "" : File.basename(@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]] + if updated_at + ActiveSupport::Deprecation.warn "ActionView::Template#updated_at is deprecated" + @updated_at = updated_at + else + @updated_at = Time.now + end + @format = format + @variant = variant @compile_mutex = Mutex.new end + deprecate :original_encoding + deprecate :updated_at + deprecate def virtual_path=(_); end + deprecate def locals=(_); end + deprecate def formats=(_); end + deprecate def formats; Array(format); end + deprecate def variants=(_); end + deprecate def variants; [variant]; end + deprecate def refresh(_); self; end + # Returns whether the underlying handler supports streaming. If so, # a streaming buffer *may* be passed when it starts rendering. def supports_streaming? @@ -165,37 +182,26 @@ module ActionView def render(view, locals, buffer = ActionView::OutputBuffer.new, &block) instrument_render_template do compile!(view) - view.run(method_name, locals, buffer, &block) + view._run(method_name, self, locals, buffer, &block) end rescue => e handle_render_error(view, e) end def type - @type ||= Types[@formats.first] if @formats.first + @type ||= Types[format] end - # Receives a view object and return a template similar to self by using @virtual_path. - # - # This method is useful if you have a template object but it does not contain its source - # anymore since it was already compiled. In such cases, all you need to do is to call - # refresh passing in the view object. - # - # Notice this method raises an error if the template to be refreshed does not have a - # virtual path set (true just for inline templates). - def refresh(view) - raise "A template needs to have a virtual path in order to be refreshed" unless @virtual_path - lookup = view.lookup_context - pieces = @virtual_path.split("/") - name = pieces.pop - partial = !!name.sub!(/^_/, "") - lookup.disable_cache do - lookup.find_template(name, [ pieces.join("/") ], partial, @locals) - end + def short_identifier + @short_identifier ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", "") : identifier end def inspect - @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", "") : identifier + "#<#{self.class.name} #{short_identifier} locals=#{@locals.inspect}>" + end + + def source + @source.to_s end # This method is responsible for properly setting the encoding of the @@ -249,11 +255,11 @@ module ActionView # 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: - [ @source, @identifier, @handler, @compiled, @original_encoding, @locals, @virtual_path, @updated_at, @formats, @variants ] + [ @source, @identifier, @handler, @compiled, @locals, @virtual_path, @updated_at, @format, @variant ] end def marshal_load(array) # :nodoc: - @source, @identifier, @handler, @compiled, @original_encoding, @locals, @virtual_path, @updated_at, @formats, @variants = *array + @source, @identifier, @handler, @compiled, @locals, @virtual_path, @updated_at, @format, @variant = *array @compile_mutex = Mutex.new end @@ -279,9 +285,6 @@ module ActionView compile(mod) end - # Just discard the source if we have a virtual path. This - # means we can get the template back. - @source = nil if @virtual_path @compiled = true end end @@ -313,6 +316,7 @@ module ActionView # Make sure that the resulting String to be eval'd is in the # encoding of the code + original_source = source source = +<<-end_src def #{method_name}(local_assigns, output_buffer) @virtual_path = #{@virtual_path.inspect};#{locals_code};#{code} @@ -333,7 +337,14 @@ module ActionView raise WrongEncodingError.new(source, Encoding.default_internal) end - mod.module_eval(source, identifier, 0) + begin + mod.module_eval(source, identifier, 0) + rescue SyntaxError + # Account for when code in the template is not syntactically valid; e.g. if we're using + # ERB and the user writes <%= foo( %>, attempting to call a helper `foo` and interpolate + # the result into the template, but missing an end parenthesis. + raise SyntaxErrorInTemplate.new(self, original_source) + end end def handle_render_error(view, e) @@ -341,12 +352,7 @@ module ActionView e.sub_template_of(self) raise e else - template = self - unless template.source - template = refresh(view) - template.encode! - end - raise Template::Error.new(template) + raise Template::Error.new(self) end end @@ -369,7 +375,7 @@ module ActionView end def identifier_method_name - inspect.tr("^a-z_", "_") + short_identifier.tr("^a-z_", "_") end def instrument(action, &block) # :doc: diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb index 4e3c02e05e..d0ea03e228 100644 --- a/actionview/lib/action_view/template/error.rb +++ b/actionview/lib/action_view/template/error.rb @@ -109,7 +109,7 @@ module ActionView end end - def annoted_source_code + def annotated_source_code source_extract(4) end @@ -138,4 +138,24 @@ module ActionView end TemplateError = Template::Error + + class SyntaxErrorInTemplate < TemplateError #:nodoc + def initialize(template, offending_code_string) + @offending_code_string = offending_code_string + super(template) + end + + def message + <<~MESSAGE + Encountered a syntax error while rendering template: check #{@offending_code_string} + MESSAGE + end + + def annotated_source_code + @offending_code_string.split("\n").map.with_index(1) { |line, index| + indentation = " " * 4 + "#{index}:#{indentation}#{line}" + } + end + end end diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb index ddaac7a100..6450513003 100644 --- a/actionview/lib/action_view/template/handlers.rb +++ b/actionview/lib/action_view/template/handlers.rb @@ -45,12 +45,12 @@ module ActionView #:nodoc: unless params.find_all { |type, _| type == :req || type == :opt }.length >= 2 ActiveSupport::Deprecation.warn <<~eowarn - Single arity template handlers are deprecated. Template handlers must + 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(", ")}) + >> #{handler}.call(#{params.map(&:last).join(", ")}) To: - >> #{handler.class}#call(#{params.map(&:last).join(", ")}, source) + >> #{handler}.call(#{params.map(&:last).join(", ")}, source) eowarn handler = LegacyHandlerWrapper.new(handler) end diff --git a/actionview/lib/action_view/template/handlers/erb/erubi.rb b/actionview/lib/action_view/template/handlers/erb/erubi.rb index 15ca202024..e602aa117a 100644 --- a/actionview/lib/action_view/template/handlers/erb/erubi.rb +++ b/actionview/lib/action_view/template/handlers/erb/erubi.rb @@ -25,9 +25,9 @@ module ActionView src = @src view = Class.new(ActionView::Base) { include action_view_erb_handler_context._routes.url_helpers - class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", @filename || "(erubi)", 0) - }.with_context(action_view_erb_handler_context) - view.run(:_template, {}, ActionView::OutputBuffer.new) + class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", defined?(@filename) ? @filename : "(erubi)", 0) + }.empty + view._run(:_template, nil, {}, ActionView::OutputBuffer.new) end private diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb index a262c6d9ad..ecd1c31e79 100644 --- a/actionview/lib/action_view/template/html.rb +++ b/actionview/lib/action_view/template/html.rb @@ -1,15 +1,21 @@ # frozen_string_literal: true +require "active_support/deprecation" + module ActionView #:nodoc: # = Action View HTML Template class Template #:nodoc: class HTML #:nodoc: - attr_accessor :type + attr_reader :type def initialize(string, type = nil) + unless type + ActiveSupport::Deprecation.warn "ActionView::Template::HTML#initialize requires a type parameter" + type = :html + end + @string = string.to_s - @type = Types[type] || type if type - @type ||= Types[:html] + @type = type end def identifier @@ -26,9 +32,12 @@ module ActionView #:nodoc: to_str end - def formats - [@type.respond_to?(:ref) ? @type.ref : @type.to_s] + def format + @type end + + def formats; Array(format); end + deprecate :formats end end end diff --git a/actionview/lib/action_view/template/inline.rb b/actionview/lib/action_view/template/inline.rb new file mode 100644 index 0000000000..44658487ea --- /dev/null +++ b/actionview/lib/action_view/template/inline.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module ActionView #:nodoc: + class Template #:nodoc: + class Inline < Template #:nodoc: + # This finalizer is needed (and exactly with a proc inside another proc) + # otherwise templates leak in development. + Finalizer = proc do |method_name, mod| # :nodoc: + proc do + mod.module_eval do + remove_possible_method method_name + end + end + end + + def compile(mod) + super + ObjectSpace.define_finalizer(self, Finalizer[method_name, mod]) + end + end + end +end diff --git a/actionview/lib/action_view/template/raw_file.rb b/actionview/lib/action_view/template/raw_file.rb new file mode 100644 index 0000000000..623351305f --- /dev/null +++ b/actionview/lib/action_view/template/raw_file.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActionView #:nodoc: + # = Action View RawFile Template + class Template #:nodoc: + class RawFile #:nodoc: + attr_accessor :type, :format + + def initialize(filename) + @filename = filename.to_s + extname = ::File.extname(filename).delete(".") + @type = Template::Types[extname] || Template::Types[:text] + @format = @type.symbol + end + + def identifier + @filename + end + + def render(*args) + ::File.read(@filename) + end + + def formats; Array(format); end + deprecate :formats + end + end +end diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 3b4594942b..ce53eb046d 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -63,26 +63,11 @@ module ActionView # Cache the templates returned by the block def cache(key, name, prefix, partial, locals) - if Resolver.caching? - @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield) - else - fresh_templates = yield - cached_templates = @data[key][name][prefix][partial][locals] - - if templates_have_changed?(cached_templates, fresh_templates) - @data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates) - else - cached_templates || NO_TEMPLATES - end - end + @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield) end def cache_query(query) # :nodoc: - if Resolver.caching? - @query_cache[query] ||= canonical_no_templates(yield) - else - yield - end + @query_cache[query] ||= canonical_no_templates(yield) end def clear @@ -112,19 +97,6 @@ module ActionView def canonical_no_templates(templates) templates.empty? ? NO_TEMPLATES : templates end - - def templates_have_changed?(cached_templates, fresh_templates) - # if either the old or new template list is empty, we don't need to (and can't) - # compare modification times, and instead just check whether the lists are different - if cached_templates.blank? || fresh_templates.blank? - return fresh_templates.blank? != cached_templates.blank? - end - - cached_templates_max_updated_at = cached_templates.map(&:updated_at).max - - # if a template has changed, it will be now be newer than all the cached templates - fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at } - end end cattr_accessor :caching, default: true @@ -143,35 +115,33 @@ module ActionView # Normalizes the arguments and passes it on to find_templates. def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = []) - cached(key, [name, prefix, partial], details, locals) do - find_templates(name, prefix, partial, details) - end - end + locals = locals.map(&:to_s).sort!.freeze - def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = []) cached(key, [name, prefix, partial], details, locals) do - find_templates(name, prefix, partial, details, true) + _find_all(name, prefix, partial, details, key, locals) end end + alias :find_all_anywhere :find_all + deprecate :find_all_anywhere + def find_all_with_query(query) # :nodoc: @cache.cache_query(query) { find_template_paths(File.join(@path, query)) } end private + def _find_all(name, prefix, partial, details, key, locals) + find_templates(name, prefix, partial, details, locals) + end + delegate :caching?, to: :class # This is what child classes implement. No defaults are needed # because Resolver guarantees that the arguments are present and # normalized. - def find_templates(name, prefix, partial, details, outside_app_allowed = false) - raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed = false) method" - end - - # Helpers that builds a path. Useful for building virtual paths. - def build_path(name, prefix, partial) - Path.build(name, prefix, partial) + def find_templates(name, prefix, partial, details, locals = []) + raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method" end # Handles templates caching. If a key is given and caching is on @@ -180,25 +150,13 @@ module ActionView # resolver is fresher before returning it. def cached(key, path_info, details, locals) name, prefix, partial = path_info - locals = locals.map(&:to_s).sort! if key @cache.cache(key, name, prefix, partial, locals) do - decorate(yield, path_info, details, locals) + yield end else - decorate(yield, path_info, details, locals) - end - end - - # Ensures all the resolver information is set in the template. - def decorate(templates, path_info, details, locals) - cached = nil - templates.each do |t| - t.locals = locals - 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)) + yield end end end @@ -209,33 +167,60 @@ module ActionView DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}" def initialize(pattern = nil) - @pattern = pattern || DEFAULT_PATTERN + if pattern + ActiveSupport::Deprecation.warn "Specifying a custom path for #{self.class} is deprecated. Implement a custom Resolver subclass instead." + @pattern = pattern + else + @pattern = DEFAULT_PATTERN + end + @unbound_templates = Concurrent::Map.new + super() + end + + def clear_cache + @unbound_templates.clear super() end private - def find_templates(name, prefix, partial, details, outside_app_allowed = false) + def _find_all(name, prefix, partial, details, key, locals) path = Path.build(name, prefix, partial) - query(path, details, details[:formats], outside_app_allowed) + query(path, details, details[:formats], locals, cache: !!key) end - def query(path, details, formats, outside_app_allowed) + def query(path, details, formats, locals, cache:) template_paths = find_template_paths_from_details(path, details) - template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed + template_paths = reject_files_external_to_app(template_paths) template_paths.map do |template| - handler, format, variant = extract_handler_and_format_and_variant(template) - - FileTemplate.new(File.expand_path(template), handler, - virtual_path: path.virtual, - format: format, - variant: variant, - updated_at: mtime(template) - ) + unbound_template = + if cache + @unbound_templates.compute_if_absent([template, path.virtual]) do + build_unbound_template(template, path.virtual) + end + else + build_unbound_template(template, path.virtual) + end + + unbound_template.bind_locals(locals) end end + def build_unbound_template(template, virtual_path) + handler, format, variant = extract_handler_and_format_and_variant(template) + source = Template::Sources::File.new(template) + + UnboundTemplate.new( + source, + template, + handler, + virtual_path: virtual_path, + format: format, + variant: variant, + ) + end + def reject_files_external_to_app(files) files.reject { |filename| !inside_path?(@path, filename) } end @@ -284,11 +269,6 @@ module ActionView entry.gsub(/[*?{}\[\]]/, '\\\\\\&') end - # Returns the file mtime from the filesystem. - def mtime(p) - File.mtime(p) - end - # Extract handler, formats and variant from path. If a format cannot be found neither # from the path, or the handler, we should return the array of formats given # to the resolver. @@ -300,51 +280,25 @@ module ActionView handler = Template.handler_for_extension(extension) format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last - format &&= Template::Types[format] + format = if format + Template::Types[format]&.ref + else + if handler.respond_to?(:default_format) # default_format can return nil + handler.default_format + else + nil + end + end + # Template::Types[format] and handler.default_format can return nil [handler, format, variant] end end - # A resolver that loads files from the filesystem. It allows setting your own - # resolving pattern. Such pattern can be a glob string supported by some variables. - # - # ==== Examples - # - # Default pattern, loads views the same way as previous versions of rails, eg. when you're - # looking for <tt>users/new</tt> it will produce query glob: <tt>users/new{.{en},}{.{html,js},}{.{erb,haml},}</tt> - # - # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}") - # - # This one allows you to keep files with different formats in separate subdirectories, - # eg. <tt>users/new.html</tt> will be loaded from <tt>users/html/new.erb</tt> or <tt>users/new.html.erb</tt>, - # <tt>users/new.js</tt> from <tt>users/js/new.erb</tt> or <tt>users/new.js.erb</tt>, etc. - # - # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}") - # - # If you don't specify a pattern then the default will be used. - # - # In order to use any of the customized resolvers above in a Rails application, you just need - # to configure ActionController::Base.view_paths in an initializer, for example: - # - # ActionController::Base.view_paths = FileSystemResolver.new( - # Rails.root.join("app/views"), - # ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}", - # ) - # - # ==== Pattern format and variables - # - # Pattern has to be a valid glob string, and it allows you to use the - # following variables: - # - # * <tt>:prefix</tt> - usually the controller path - # * <tt>:action</tt> - name of the action - # * <tt>:locale</tt> - possible locale versions - # * <tt>:formats</tt> - possible request formats (for example html, json, xml...) - # * <tt>:variants</tt> - possible request variants (for example phone, tablet...) - # * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...) - # + # A resolver that loads files from the filesystem. class FileSystemResolver < PathResolver + attr_reader :path + def initialize(path, pattern = nil) raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver) super(pattern) @@ -364,6 +318,10 @@ module ActionView # An Optimized resolver for Rails' most common case. class OptimizedFileSystemResolver < FileSystemResolver #:nodoc: + def initialize(path) + super(path) + end + private def find_template_paths_from_details(path, details) @@ -419,12 +377,18 @@ module ActionView # The same as FileSystemResolver but does not allow templates to store # a virtual path since it is invalid for such resolvers. class FallbackFileSystemResolver < FileSystemResolver #:nodoc: + private_class_method :new + def self.instances [new(""), new("/")] end - def decorate(*) - super.each { |t| t.virtual_path = nil } + def build_unbound_template(template, _) + super(template, nil) + end + + def reject_files_external_to_app(files) + files end end end diff --git a/actionview/lib/action_view/template/sources.rb b/actionview/lib/action_view/template/sources.rb new file mode 100644 index 0000000000..8b89535741 --- /dev/null +++ b/actionview/lib/action_view/template/sources.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module ActionView + class Template + module Sources + extend ActiveSupport::Autoload + + eager_autoload do + autoload :File + end + end + end +end diff --git a/actionview/lib/action_view/template/sources/file.rb b/actionview/lib/action_view/template/sources/file.rb new file mode 100644 index 0000000000..2d3a7dec7f --- /dev/null +++ b/actionview/lib/action_view/template/sources/file.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActionView + class Template + module Sources + class File + def initialize(filename) + @filename = filename + end + + def to_s + ::File.binread @filename + end + end + end + end +end diff --git a/actionview/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb index f8d6c2811f..c5fd55f1b3 100644 --- a/actionview/lib/action_view/template/text.rb +++ b/actionview/lib/action_view/template/text.rb @@ -8,7 +8,6 @@ module ActionView #:nodoc: def initialize(string) @string = string.to_s - @type = Types[:text] end def identifier @@ -25,9 +24,12 @@ module ActionView #:nodoc: to_str end - def formats - [@type.ref] + def format + :text end + + def formats; Array(format); end + deprecate :formats end end end diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index e14f7aaec7..3a75633743 100644 --- a/actionview/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb @@ -284,7 +284,7 @@ module ActionView def respond_to_missing?(name, include_private = false) begin - routes = @controller.respond_to?(:_routes) && @controller._routes + routes = defined?(@controller) && @controller.respond_to?(:_routes) && @controller._routes rescue # Don't call routes, if there is an error on _routes call end diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb index d6203b95c5..1bedf44934 100644 --- a/actionview/lib/action_view/testing/resolvers.rb +++ b/actionview/lib/action_view/testing/resolvers.rb @@ -23,23 +23,22 @@ module ActionView #:nodoc: private - def query(path, exts, _, _) + def query(path, exts, _, locals, cache:) query = +"" - EXTENSIONS.each_key do |ext| - query << "(" << exts[ext].map { |e| e && Regexp.escape(".#{e}") }.join("|") << "|)" + EXTENSIONS.each do |ext, prefix| + query << "(" << exts[ext].map { |e| e && Regexp.escape("#{prefix}#{e}") }.join("|") << "|)" end query = /^(#{Regexp.escape(path)})#{query}$/ templates = [] - @hash.each do |_path, array| - source, updated_at = array + @hash.each do |_path, source| next unless query.match?(_path) handler, format, variant = extract_handler_and_format_and_variant(_path) templates << Template.new(source, _path, handler, virtual_path: path.virtual, format: format, variant: variant, - updated_at: updated_at + locals: locals ) end @@ -48,9 +47,9 @@ module ActionView #:nodoc: end class NullResolver < PathResolver - def query(path, exts, _, _) + def query(path, exts, _, locals, cache:) handler, format, variant = extract_handler_and_format_and_variant(path) - [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: format, variant: variant)] + [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: format, variant: variant, locals: locals)] end end end diff --git a/actionview/lib/action_view/unbound_template.rb b/actionview/lib/action_view/unbound_template.rb new file mode 100644 index 0000000000..db69b6d016 --- /dev/null +++ b/actionview/lib/action_view/unbound_template.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "concurrent/map" + +module ActionView + class UnboundTemplate + def initialize(source, identifer, handler, options) + @source = source + @identifer = identifer + @handler = handler + @options = options + + @templates = Concurrent::Map.new(initial_capacity: 2) + end + + def bind_locals(locals) + @templates[locals] ||= build_template(locals) + end + + private + + def build_template(locals) + options = @options.merge(locals: locals) + Template.new( + @source, + @identifer, + @handler, + options + ) + end + end +end diff --git a/actionview/package.json b/actionview/package.json index eb2a9f879e..74cdcfec43 100644 --- a/actionview/package.json +++ b/actionview/package.json @@ -1,6 +1,6 @@ { "name": "@rails/ujs", - "version": "6.0.0-beta1", + "version": "6.1.0-alpha", "description": "Ruby on Rails unobtrusive scripting adapter", "main": "lib/assets/compiled/rails-ujs.js", "files": [ diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb index b649b3c9dd..2c1b277b41 100644 --- a/actionview/test/abstract_unit.rb +++ b/actionview/test/abstract_unit.rb @@ -60,7 +60,7 @@ module RenderERBUtils string.strip, "test template", ActionView::Template.handler_for_extension(:erb), - {}) + format: :html, locals: []) view = ActionView::Base.with_empty_template_cache template.render(view.empty, {}).strip @@ -205,3 +205,5 @@ class ActiveSupport::TestCase skip message if defined?(JRUBY_VERSION) end end + +require_relative "../../tools/test_common" diff --git a/actionview/test/actionpack/abstract/abstract_controller_test.rb b/actionview/test/actionpack/abstract/abstract_controller_test.rb index 4d4e2b8ef2..eecc19d413 100644 --- a/actionview/test/actionpack/abstract/abstract_controller_test.rb +++ b/actionview/test/actionpack/abstract/abstract_controller_test.rb @@ -229,11 +229,11 @@ module AbstractController end class ActionMissingRespondToActionController < AbstractController::Base - # No actions - private - def action_missing(action_name) - self.response_body = "success" - end + # No actions + private + def action_missing(action_name) + self.response_body = "success" + end end class RespondToActionController < AbstractController::Base diff --git a/actionview/test/actionpack/abstract/render_test.rb b/actionview/test/actionpack/abstract/render_test.rb index d863548a5c..e4e8ac93b2 100644 --- a/actionview/test/actionpack/abstract/render_test.rb +++ b/actionview/test/actionpack/abstract/render_test.rb @@ -26,7 +26,7 @@ module AbstractController end def file - render file: "some/file" + ActiveSupport::Deprecation.silence { render file: "some/file" } end def inline diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb index 838b564c5d..f946e4360d 100644 --- a/actionview/test/actionpack/controller/layout_test.rb +++ b/actionview/test/actionpack/controller/layout_test.rb @@ -233,7 +233,9 @@ class LayoutSetInResponseTest < ActionController::TestCase def test_absolute_pathed_layout @controller = AbsolutePathLayoutController.new - get :hello + assert_deprecated do + get :hello + end assert_equal "layout_test.erb hello.erb", @response.body.strip end end diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb index 727d3fbc1a..c8ce7366d1 100644 --- a/actionview/test/actionpack/controller/render_test.rb +++ b/actionview/test/actionpack/controller/render_test.rb @@ -174,6 +174,10 @@ class TestController < ActionController::Base render inline: "<%= controller_name %>" end + def inline_rendered_format_without_format + render inline: "test" + end + # :ported: def render_custom_code render plain: "hello world", status: 404 @@ -659,6 +663,7 @@ class RenderTest < ActionController::TestCase get :hello_world_from_rxml_using_action, to: "test#hello_world_from_rxml_using_action" get :hello_world_from_rxml_using_template, to: "test#hello_world_from_rxml_using_template" get :hello_world_with_layout_false, to: "test#hello_world_with_layout_false" + get :inline_rendered_format_without_format, to: "test#inline_rendered_format_without_format" get :layout_overriding_layout, to: "test#layout_overriding_layout" get :layout_test, to: "test#layout_test" get :layout_test_with_different_layout, to: "test#layout_test_with_different_layout" @@ -867,48 +872,64 @@ class RenderTest < ActionController::TestCase # :ported: def test_render_file_with_instance_variables - get :render_file_with_instance_variables + assert_deprecated do + get :render_file_with_instance_variables + end assert_equal "The secret is in the sauce\n", @response.body end def test_render_file - get :hello_world_file + assert_deprecated do + get :hello_world_file + end assert_equal "Hello world!", @response.body end # :ported: def test_render_file_not_using_full_path - get :render_file_not_using_full_path + assert_deprecated do + get :render_file_not_using_full_path + end assert_equal "The secret is in the sauce\n", @response.body end # :ported: def test_render_file_not_using_full_path_with_dot_in_path - get :render_file_not_using_full_path_with_dot_in_path + assert_deprecated do + get :render_file_not_using_full_path_with_dot_in_path + end assert_equal "The secret is in the sauce\n", @response.body end # :ported: def test_render_file_using_pathname - get :render_file_using_pathname + assert_deprecated do + get :render_file_using_pathname + end assert_equal "The secret is in the sauce\n", @response.body end # :ported: def test_render_file_with_locals - get :render_file_with_locals + assert_deprecated do + get :render_file_with_locals + end assert_equal "The secret is in the sauce\n", @response.body end # :ported: def test_render_file_as_string_with_locals - get :render_file_as_string_with_locals + assert_deprecated do + get :render_file_as_string_with_locals + end assert_equal "The secret is in the sauce\n", @response.body end # :assessed: def test_render_file_from_template - get :render_file_from_template + assert_deprecated do + get :render_file_from_template + end assert_equal "The secret is in the sauce\n", @response.body end @@ -1015,6 +1036,12 @@ class RenderTest < ActionController::TestCase assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body end + def test_rendered_format_without_format + get :inline_rendered_format_without_format + assert_equal "test", @response.body + assert_equal "text/html", @response.content_type + end + def test_partials_list get :partials_list assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body @@ -1122,11 +1149,19 @@ class RenderTest < ActionController::TestCase end def test_bad_render_to_string_still_throws_exception - assert_raise(ActionView::MissingTemplate) { get :render_to_string_with_exception } + assert_deprecated do + assert_raise(ActionView::MissingTemplate) do + get :render_to_string_with_exception + end + end end def test_render_to_string_that_throws_caught_exception_doesnt_break_assigns - assert_nothing_raised { get :render_to_string_with_caught_exception } + assert_deprecated do + assert_nothing_raised do + get :render_to_string_with_caught_exception + end + end assert_equal "i'm before the render", @controller.instance_variable_get(:@before) assert_equal "i'm after the render", @controller.instance_variable_get(:@after) end diff --git a/actionview/test/actionpack/controller/view_paths_test.rb b/actionview/test/actionpack/controller/view_paths_test.rb index c5238dd746..4449d08496 100644 --- a/actionview/test/actionpack/controller/view_paths_test.rb +++ b/actionview/test/actionpack/controller/view_paths_test.rb @@ -143,8 +143,9 @@ class ViewLoadPathsTest < ActionController::TestCase "Decorated body", template.identifier, template.handler, - virtual_path: template.virtual_path, - format: template.formats + virtual_path: template.virtual_path, + format: template.format, + locals: template.locals ) end end diff --git a/actionview/test/activerecord/relation_cache_test.rb b/actionview/test/activerecord/relation_cache_test.rb index a6befc3ee5..6fe83dcb9a 100644 --- a/actionview/test/activerecord/relation_cache_test.rb +++ b/actionview/test/activerecord/relation_cache_test.rb @@ -11,13 +11,14 @@ class RelationCacheTest < ActionView::TestCase lookup_context = ActionView::LookupContext.new(view_paths, {}, ["test"]) @view_renderer = ActionView::Renderer.new(lookup_context) @virtual_path = "path" + @current_template = lookup_context.find "test/hello_world" controller.cache_store = ActiveSupport::Cache::MemoryStore.new end def test_cache_relation_other cache(Project.all) { concat("Hello World") } - assert_equal "Hello World", controller.cache_store.read("views/path/projects-#{Project.count}") + assert_equal "Hello World", controller.cache_store.read("views/test/hello_world:fa9482a68ce25bf7589b8eddad72f736/projects-#{Project.count}") end def view_cache_dependencies; []; end diff --git a/actionview/test/fixtures/actionpack/test/hello.builder b/actionview/test/fixtures/actionpack/test/hello.builder index b8ab17ad5b..4c34ee85f0 100644 --- a/actionview/test/fixtures/actionpack/test/hello.builder +++ b/actionview/test/fixtures/actionpack/test/hello.builder @@ -1,4 +1,4 @@ xml.html do xml.p "Hello #{@name}" - xml << render(file: "test/greeting") + xml << render(template: "test/greeting") end diff --git a/actionview/test/fixtures/test/_cached_set.erb b/actionview/test/fixtures/test/_cached_set.erb new file mode 100644 index 0000000000..cd492fc519 --- /dev/null +++ b/actionview/test/fixtures/test/_cached_set.erb @@ -0,0 +1 @@ +<%= cached_set.first %> | <%= cached_set.second %> | <%= cached_set.third %> | <%= cached_set.fourth %> | <%= cached_set.fifth %> diff --git a/actionview/test/fixtures/test/_first.html.erb b/actionview/test/fixtures/test/_first.html.erb new file mode 100644 index 0000000000..2e2c825acb --- /dev/null +++ b/actionview/test/fixtures/test/_first.html.erb @@ -0,0 +1 @@ +"HTML" diff --git a/actionview/test/fixtures/test/_first.xml.erb b/actionview/test/fixtures/test/_first.xml.erb new file mode 100644 index 0000000000..9cf4f4ec85 --- /dev/null +++ b/actionview/test/fixtures/test/_first.xml.erb @@ -0,0 +1 @@ +"XML" diff --git a/actionview/test/fixtures/test/_first_layer.html.erb b/actionview/test/fixtures/test/_first_layer.html.erb new file mode 100644 index 0000000000..c1f1acb410 --- /dev/null +++ b/actionview/test/fixtures/test/_first_layer.html.erb @@ -0,0 +1,4 @@ +{"format":"HTML", "children": +[ + <%= render(partial: "first").chomp.html_safe %>, +]} diff --git a/actionview/test/fixtures/test/_first_layer.xml.erb b/actionview/test/fixtures/test/_first_layer.xml.erb new file mode 100644 index 0000000000..b8581bbbfc --- /dev/null +++ b/actionview/test/fixtures/test/_first_layer.xml.erb @@ -0,0 +1,4 @@ +{"format":"XML", "children": +[ + <%= render(partial: "first").chomp.html_safe %> +]} diff --git a/actionview/test/fixtures/test/_second.html.erb b/actionview/test/fixtures/test/_second.html.erb new file mode 100644 index 0000000000..2e2c825acb --- /dev/null +++ b/actionview/test/fixtures/test/_second.html.erb @@ -0,0 +1 @@ +"HTML" diff --git a/actionview/test/fixtures/test/_second.xml.erb b/actionview/test/fixtures/test/_second.xml.erb new file mode 100644 index 0000000000..9cf4f4ec85 --- /dev/null +++ b/actionview/test/fixtures/test/_second.xml.erb @@ -0,0 +1 @@ +"XML" diff --git a/actionview/test/fixtures/test/_second_layer.html.erb b/actionview/test/fixtures/test/_second_layer.html.erb new file mode 100644 index 0000000000..307706abd2 --- /dev/null +++ b/actionview/test/fixtures/test/_second_layer.html.erb @@ -0,0 +1,4 @@ +{"format":"HTML", "children": +[ + <%= render(partial: "first").chomp.html_safe %> +]} diff --git a/actionview/test/fixtures/test/_second_layer.xml.erb b/actionview/test/fixtures/test/_second_layer.xml.erb new file mode 100644 index 0000000000..b8581bbbfc --- /dev/null +++ b/actionview/test/fixtures/test/_second_layer.xml.erb @@ -0,0 +1,4 @@ +{"format":"XML", "children": +[ + <%= render(partial: "first").chomp.html_safe %> +]} diff --git a/actionview/test/fixtures/test/hello.builder b/actionview/test/fixtures/test/hello.builder index b8ab17ad5b..4c34ee85f0 100644 --- a/actionview/test/fixtures/test/hello.builder +++ b/actionview/test/fixtures/test/hello.builder @@ -1,4 +1,4 @@ xml.html do xml.p "Hello #{@name}" - xml << render(file: "test/greeting") + xml << render(template: "test/greeting") end diff --git a/actionview/test/fixtures/test/layout_render_file.erb b/actionview/test/fixtures/test/layout_render_file.erb index 2f8e921c5f..0477743dc4 100644 --- a/actionview/test/fixtures/test/layout_render_file.erb +++ b/actionview/test/fixtures/test/layout_render_file.erb @@ -1,2 +1,2 @@ <% content_for :title do %>title<% end -%> -<%= render :file => 'layouts/yield' -%>
\ No newline at end of file +<%= render template: 'layouts/yield' -%> diff --git a/actionview/test/fixtures/test/mixing_formats.html.erb b/actionview/test/fixtures/test/mixing_formats.html.erb new file mode 100644 index 0000000000..c65cdd7dd4 --- /dev/null +++ b/actionview/test/fixtures/test/mixing_formats.html.erb @@ -0,0 +1,5 @@ +{"format":"HTML", "children": +[ + <%= render(partial: "first", formats: :xml).chomp.html_safe %>, + <%= render(partial: "second").chomp.html_safe %> +]} diff --git a/actionview/test/fixtures/test/mixing_formats_deep.html.erb b/actionview/test/fixtures/test/mixing_formats_deep.html.erb new file mode 100644 index 0000000000..e328887eeb --- /dev/null +++ b/actionview/test/fixtures/test/mixing_formats_deep.html.erb @@ -0,0 +1,5 @@ +{"format":"HTML", "children": +[ + <%= render(partial: "first_layer", formats: :xml).chomp.html_safe %>, + <%= render(partial: "second_layer").chomp.html_safe %> +]} diff --git a/actionview/test/fixtures/test/syntax_error.html.erb b/actionview/test/fixtures/test/syntax_error.html.erb new file mode 100644 index 0000000000..4004a2b187 --- /dev/null +++ b/actionview/test/fixtures/test/syntax_error.html.erb @@ -0,0 +1,4 @@ +<%= foo( + 1, + 2, +%> diff --git a/actionview/test/template/active_model_helper_test.rb b/actionview/test/template/active_model_helper_test.rb index 36afed6dd6..c6a3c9064f 100644 --- a/actionview/test/template/active_model_helper_test.rb +++ b/actionview/test/template/active_model_helper_test.rb @@ -20,11 +20,11 @@ class ActiveModelHelperTest < ActionView::TestCase super @post = Post.new - @post.errors[:author_name] << "can't be empty" - @post.errors[:body] << "foo" - @post.errors[:category] << "must exist" - @post.errors[:published] << "must be accepted" - @post.errors[:updated_at] << "bar" + assert_deprecated { @post.errors[:author_name] << "can't be empty" } + assert_deprecated { @post.errors[:body] << "foo" } + assert_deprecated { @post.errors[:category] << "must exist" } + assert_deprecated { @post.errors[:published] << "must be accepted" } + assert_deprecated { @post.errors[:updated_at] << "bar" } @post.author_name = "" @post.body = "Back to the hill and over it again!" diff --git a/actionview/test/template/compiled_templates_test.rb b/actionview/test/template/compiled_templates_test.rb index 6375555bb4..d7f2e8ee07 100644 --- a/actionview/test/template/compiled_templates_test.rb +++ b/actionview/test/template/compiled_templates_test.rb @@ -24,7 +24,7 @@ class CompiledTemplatesTest < ActiveSupport::TestCase def test_template_with_ruby_keyword_locals assert_equal "The class is foo", - render(file: "test/render_file_with_ruby_keyword_locals", locals: { class: "foo" }) + render(template: "test/render_file_with_ruby_keyword_locals", locals: { class: "foo" }) end def test_template_with_invalid_identifier_locals @@ -34,7 +34,7 @@ class CompiledTemplatesTest < ActiveSupport::TestCase "d-a-s-h-e-s": "", "white space": "", } - assert_equal locals.inspect, render(file: "test/render_file_inspect_local_assigns", locals: locals) + assert_equal locals.inspect, render(template: "test/render_file_inspect_local_assigns", locals: locals) end def test_template_with_delegation_reserved_keywords @@ -44,36 +44,36 @@ class CompiledTemplatesTest < ActiveSupport::TestCase args: "three", block: "four", } - assert_equal "one two three four", render(file: "test/test_template_with_delegation_reserved_keywords", locals: locals) + assert_equal "one two three four", render(template: "test/test_template_with_delegation_reserved_keywords", locals: locals) end def test_template_with_unicode_identifier - assert_equal "🎂", render(file: "test/render_file_unicode_local", locals: { 🎃: "🎂" }) + assert_equal "🎂", render(template: "test/render_file_unicode_local", locals: { 🎃: "🎂" }) end def test_template_with_instance_variable_identifier - assert_equal "bar", render(file: "test/render_file_instance_variable", locals: { "@foo": "bar" }) + assert_equal "bar", render(template: "test/render_file_instance_variable", locals: { "@foo": "bar" }) end def test_template_gets_recompiled_when_using_different_keys_in_local_assigns - assert_equal "one", render(file: "test/render_file_with_locals_and_default") - assert_equal "two", render(file: "test/render_file_with_locals_and_default", locals: { secret: "two" }) + assert_equal "one", render(template: "test/render_file_with_locals_and_default") + assert_equal "two", render(template: "test/render_file_with_locals_and_default", locals: { secret: "two" }) end def test_template_changes_are_not_reflected_with_cached_templates - assert_equal "Hello world!", render(file: "test/hello_world") + assert_equal "Hello world!", render(template: "test/hello_world") modify_template "test/hello_world.erb", "Goodbye world!" do - assert_equal "Hello world!", render(file: "test/hello_world") + assert_equal "Hello world!", render(template: "test/hello_world") end - assert_equal "Hello world!", render(file: "test/hello_world") + assert_equal "Hello world!", render(template: "test/hello_world") end def test_template_changes_are_reflected_with_uncached_templates - assert_equal "Hello world!", render_without_cache(file: "test/hello_world") + assert_equal "Hello world!", render_without_cache(template: "test/hello_world") modify_template "test/hello_world.erb", "Goodbye world!" do - assert_equal "Goodbye world!", render_without_cache(file: "test/hello_world") + assert_equal "Goodbye world!", render_without_cache(template: "test/hello_world") end - assert_equal "Hello world!", render_without_cache(file: "test/hello_world") + assert_equal "Hello world!", render_without_cache(template: "test/hello_world") end private diff --git a/actionview/test/template/csp_helper_test.rb b/actionview/test/template/csp_helper_test.rb new file mode 100644 index 0000000000..1b7fd4665f --- /dev/null +++ b/actionview/test/template/csp_helper_test.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "abstract_unit" + +class CspHelperWithCspEnabledTest < ActionView::TestCase + tests ActionView::Helpers::CspHelper + + def content_security_policy_nonce + "iyhD0Yc0W+c=" + end + + def content_security_policy? + true + end + + def test_csp_meta_tag + assert_equal "<meta name=\"csp-nonce\" content=\"iyhD0Yc0W+c=\" />", csp_meta_tag + end + + def test_csp_meta_tag_with_options + assert_equal "<meta property=\"csp-nonce\" name=\"csp-nonce\" content=\"iyhD0Yc0W+c=\" />", csp_meta_tag(property: "csp-nonce") + end +end + +class CspHelperWithCspDisabledTest < ActionView::TestCase + tests ActionView::Helpers::CspHelper + + def content_security_policy? + false + end + + def test_csp_meta_tag + assert_nil csp_meta_tag + end +end diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb index ddaa7febb3..4515afdfff 100644 --- a/actionview/test/template/digestor_test.rb +++ b/actionview/test/template/digestor_test.rb @@ -7,9 +7,8 @@ require "action_view/dependency_tracker" class FixtureFinder < ActionView::LookupContext FIXTURES_DIR = File.expand_path("../fixtures/digestor", __dir__) - def initialize(details = {}) - super(ActionView::PathSet.new(["digestor", "digestor/api"]), details, []) - @rendered_format = :html + def self.build(details = {}) + new(ActionView::PathSet.new(["digestor", "digestor/api"]), details, []) end end @@ -146,13 +145,12 @@ class TemplateDigestorTest < ActionView::TestCase end def test_nested_template_deps_with_non_default_rendered_format - finder.rendered_format = nil nested_deps = [{ "comments/comments" => ["comments/comment"] }] assert_equal nested_deps, nested_dependencies("messages/thread") end def test_template_formats_of_nested_deps_with_non_default_rendered_format - finder.rendered_format = nil + @finder = finder.with_prepended_formats([:json]) assert_equal [:json], tree_template_formats("messages/thread").uniq end @@ -161,12 +159,10 @@ class TemplateDigestorTest < ActionView::TestCase end def test_template_dependencies_with_fallback_from_js_to_html_format - finder.rendered_format = :js assert_equal ["comments/comment"], dependencies("comments/show") end def test_template_digest_with_fallback_from_js_to_html_format - finder.rendered_format = :js assert_digest_difference("comments/show") do change_template("comments/_comment") end @@ -219,14 +215,14 @@ class TemplateDigestorTest < ActionView::TestCase def test_details_are_included_in_cache_key # Cache the template digest. - @finder = FixtureFinder.new(formats: [:html]) + @finder = FixtureFinder.build(formats: [:html]) 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 = FixtureFinder.new + @finder = FixtureFinder.build # The cache is busted. assert_not_equal old_digest, digest("events/_event") @@ -330,12 +326,14 @@ class TemplateDigestorTest < ActionView::TestCase def assert_digest_difference(template_name, options = {}) previous_digest = digest(template_name, options) + finder.view_paths.each(&:clear_cache) finder.digest_cache.clear yield assert_not_equal previous_digest, digest(template_name, options), "digest didn't change" finder.digest_cache.clear + finder.view_paths.each(&:clear_cache) end def digest(template_name, options = {}) @@ -343,9 +341,14 @@ class TemplateDigestorTest < ActionView::TestCase finder_options = options.extract!(:variants, :format) finder.variants = finder_options[:variants] || [] - finder.rendered_format = finder_options[:format] if finder_options[:format] - ActionView::Digestor.digest(name: template_name, finder: finder, dependencies: (options[:dependencies] || [])) + finder_with_formats = if finder_options[:format] + finder.with_prepended_formats(Array(finder_options[:format])) + else + finder + end + + ActionView::Digestor.digest(name: template_name, format: finder_options[:format], finder: finder_with_formats, dependencies: (options[:dependencies] || [])) end def dependencies(template_name) @@ -360,7 +363,7 @@ class TemplateDigestorTest < ActionView::TestCase def tree_template_formats(template_name) tree = ActionView::Digestor.tree(template_name, finder) - tree.flatten.map(&:template).compact.flat_map(&:formats) + tree.flatten.map(&:template).compact.map(&:format) end def disable_resolver_caching @@ -371,7 +374,7 @@ class TemplateDigestorTest < ActionView::TestCase end def finder - @finder ||= FixtureFinder.new + @finder ||= FixtureFinder.build end def change_template(template_name, variant = nil) diff --git a/actionview/test/template/fallback_file_system_resolver_test.rb b/actionview/test/template/fallback_file_system_resolver_test.rb new file mode 100644 index 0000000000..fa770f3a15 --- /dev/null +++ b/actionview/test/template/fallback_file_system_resolver_test.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "abstract_unit" + +class FallbackFileSystemResolverTest < ActiveSupport::TestCase + def setup + @root_resolver = ActionView::FallbackFileSystemResolver.send(:new, "/") + end + + def test_should_have_no_virtual_path + templates = @root_resolver.find_all("hello_world.erb", "#{FIXTURE_LOAD_PATH}/test", false, locale: [], formats: [:html], variants: [], handlers: [:erb]) + assert_equal 1, templates.size + assert_equal "Hello world!", templates[0].source + assert_nil templates[0].virtual_path + end +end diff --git a/actionview/test/template/file_system_resolver_test.rb b/actionview/test/template/file_system_resolver_test.rb new file mode 100644 index 0000000000..aa03fdcb13 --- /dev/null +++ b/actionview/test/template/file_system_resolver_test.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "template/resolver_shared_tests" + +class FileSystemResolverTest < ActiveSupport::TestCase + include ResolverSharedTests + + def resolver + ActionView::FileSystemResolver.new(tmpdir) + end +end diff --git a/actionview/test/template/form_helper/form_with_test.rb b/actionview/test/template/form_helper/form_with_test.rb index f84c9b2b73..42069340f1 100644 --- a/actionview/test/template/form_helper/form_with_test.rb +++ b/actionview/test/template/form_helper/form_with_test.rb @@ -994,7 +994,7 @@ class FormWithActsLikeFormForTest < FormWithTest end def test_submit_with_object_as_new_record_and_locale_strings - with_locale :submit do + I18n.with_locale :submit do @post.persisted = false @post.stub(:to_key, nil) do form_with(model: @post) do |f| @@ -1011,7 +1011,7 @@ class FormWithActsLikeFormForTest < FormWithTest end def test_submit_with_object_as_existing_record_and_locale_strings - with_locale :submit do + I18n.with_locale :submit do form_with(model: @post) do |f| concat f.submit end @@ -1025,7 +1025,7 @@ class FormWithActsLikeFormForTest < FormWithTest end def test_submit_without_object_and_locale_strings - with_locale :submit do + I18n.with_locale :submit do form_with(scope: :post) do |f| concat f.submit class: "extra" end @@ -1039,7 +1039,7 @@ class FormWithActsLikeFormForTest < FormWithTest end def test_submit_with_object_which_is_overwritten_by_scope_option - with_locale :submit do + I18n.with_locale :submit do form_with(model: @post, scope: :another_post) do |f| concat f.submit end @@ -1054,7 +1054,7 @@ class FormWithActsLikeFormForTest < FormWithTest def test_submit_with_object_which_is_namespaced blog_post = Blog::Post.new("And his name will be forty and four.", 44) - with_locale :submit do + I18n.with_locale :submit do form_with(model: blog_post) do |f| concat f.submit end @@ -2357,11 +2357,4 @@ class FormWithActsLikeFormForTest < FormWithTest 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_helper_test.rb b/actionview/test/template/form_helper_test.rb index 5972946074..91052e5ae2 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -203,31 +203,31 @@ class FormHelperTest < ActionView::TestCase end def test_label_with_locales_strings - with_locale :label do + I18n.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 - with_locale :label do + I18n.with_locale :label do assert_dom_equal('<label for="post_cost">Total cost</label>', label(:post, :cost)) end end def test_label_with_human_attribute_name_and_options - with_locale :label do + I18n.with_locale :label do assert_dom_equal('<label for="post_language_spanish">Espanol</label>', label(:post, :language, value: "spanish")) end end def test_label_with_locales_symbols - with_locale :label do + I18n.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 - with_locale :label do + I18n.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") @@ -236,13 +236,13 @@ class FormHelperTest < ActionView::TestCase end def test_label_with_locales_and_value - with_locale :label do + I18n.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 - with_locale :label do + I18n.with_locale :label do form_for(@post, html: { id: "create-post" }) do |f| f.fields_for(:comments) do |cf| concat cf.label(:body) @@ -258,7 +258,7 @@ class FormHelperTest < ActionView::TestCase end def test_label_with_locales_fallback_and_nested_attributes - with_locale :label do + I18n.with_locale :label do form_for(@post, html: { id: "create-post" }) do |f| f.fields_for(:tags) do |cf| concat cf.label(:value) @@ -358,7 +358,7 @@ class FormHelperTest < ActionView::TestCase end def test_label_with_block_and_builder - with_locale :label do + I18n.with_locale :label do assert_dom_equal( '<label for="post_body"><b>Write entire text here</b></label>', label(:post, :body) { |b| raw("<b>#{b.translation}</b>") } @@ -381,7 +381,7 @@ class FormHelperTest < ActionView::TestCase end def test_label_with_to_model_and_overridden_model_name - with_locale :label do + I18n.with_locale :label do assert_dom_equal( %{<label for="post_delegator_title">Delegate model_name title</label>}, label(:post_delegator, :title) @@ -390,19 +390,19 @@ class FormHelperTest < ActionView::TestCase end def test_text_field_placeholder_without_locales - with_locale :placeholder do + I18n.with_locale :placeholder do assert_dom_equal('<input id="post_body" name="post[body]" placeholder="Body" type="text" value="Back to the hill and over it again!" />', text_field(:post, :body, placeholder: true)) end end def test_text_field_placeholder_with_locales - with_locale :placeholder do + I18n.with_locale :placeholder do assert_dom_equal('<input id="post_title" name="post[title]" placeholder="What is this about?" type="text" value="Hello World" />', text_field(:post, :title, placeholder: true)) end end def test_text_field_placeholder_with_locales_and_to_model - with_locale :placeholder do + I18n.with_locale :placeholder do assert_dom_equal( '<input id="post_delegator_title" name="post_delegator[title]" placeholder="Delegate model_name title" type="text" value="Hello World" />', text_field(:post_delegator, :title, placeholder: true) @@ -411,7 +411,7 @@ class FormHelperTest < ActionView::TestCase end def test_text_field_placeholder_with_human_attribute_name - with_locale :placeholder do + I18n.with_locale :placeholder do assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="Total cost" type="text" />', text_field(:post, :cost, placeholder: true)) end end @@ -424,25 +424,25 @@ class FormHelperTest < ActionView::TestCase end def test_text_field_placeholder_with_string_value - with_locale :placeholder do + I18n.with_locale :placeholder do assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="HOW MUCH?" type="text" />', text_field(:post, :cost, placeholder: "HOW MUCH?")) end end def test_text_field_placeholder_with_human_attribute_name_and_value - with_locale :placeholder do + I18n.with_locale :placeholder do assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="Pounds" type="text" />', text_field(:post, :cost, placeholder: :uk)) end end def test_text_field_placeholder_with_locales_and_value - with_locale :placeholder do + I18n.with_locale :placeholder do assert_dom_equal('<input id="post_written_on" name="post[written_on]" placeholder="Escrito en" type="text" value="2004-06-15" />', text_field(:post, :written_on, placeholder: :spanish)) end end def test_text_field_placeholder_with_locales_and_nested_attributes - with_locale :placeholder do + I18n.with_locale :placeholder do form_for(@post, html: { id: "create-post" }) do |f| f.fields_for(:comments) do |cf| concat cf.text_field(:body, placeholder: true) @@ -458,7 +458,7 @@ class FormHelperTest < ActionView::TestCase end def test_text_field_placeholder_with_locales_fallback_and_nested_attributes - with_locale :placeholder do + I18n.with_locale :placeholder do form_for(@post, html: { id: "create-post" }) do |f| f.fields_for(:tags) do |cf| concat cf.text_field(:value, placeholder: true) @@ -861,7 +861,7 @@ class FormHelperTest < ActionView::TestCase end def test_text_area_placeholder_without_locales - with_locale :placeholder do + I18n.with_locale :placeholder do assert_dom_equal( %{<textarea id="post_body" name="post[body]" placeholder="Body">\nBack to the hill and over it again!</textarea>}, text_area(:post, :body, placeholder: true) @@ -870,7 +870,7 @@ class FormHelperTest < ActionView::TestCase end def test_text_area_placeholder_with_locales - with_locale :placeholder do + I18n.with_locale :placeholder do assert_dom_equal( %{<textarea id="post_title" name="post[title]" placeholder="What is this about?">\nHello World</textarea>}, text_area(:post, :title, placeholder: true) @@ -879,7 +879,7 @@ class FormHelperTest < ActionView::TestCase end def test_text_area_placeholder_with_human_attribute_name - with_locale :placeholder do + I18n.with_locale :placeholder do assert_dom_equal( %{<textarea id="post_cost" name="post[cost]" placeholder="Total cost">\n</textarea>}, text_area(:post, :cost, placeholder: true) @@ -888,7 +888,7 @@ class FormHelperTest < ActionView::TestCase end def test_text_area_placeholder_with_string_value - with_locale :placeholder do + I18n.with_locale :placeholder do assert_dom_equal( %{<textarea id="post_cost" name="post[cost]" placeholder="HOW MUCH?">\n</textarea>}, text_area(:post, :cost, placeholder: "HOW MUCH?") @@ -897,7 +897,7 @@ class FormHelperTest < ActionView::TestCase end def test_text_area_placeholder_with_human_attribute_name_and_value - with_locale :placeholder do + I18n.with_locale :placeholder do assert_dom_equal( %{<textarea id="post_cost" name="post[cost]" placeholder="Pounds">\n</textarea>}, text_area(:post, :cost, placeholder: :uk) @@ -906,7 +906,7 @@ class FormHelperTest < ActionView::TestCase end def test_text_area_placeholder_with_locales_and_value - with_locale :placeholder do + I18n.with_locale :placeholder do assert_dom_equal( %{<textarea id="post_written_on" name="post[written_on]" placeholder="Escrito en">\n2004-06-15</textarea>}, text_area(:post, :written_on, placeholder: :spanish) @@ -915,7 +915,7 @@ class FormHelperTest < ActionView::TestCase end def test_text_area_placeholder_with_locales_and_nested_attributes - with_locale :placeholder do + I18n.with_locale :placeholder do form_for(@post, html: { id: "create-post" }) do |f| f.fields_for(:comments) do |cf| concat cf.text_area(:body, placeholder: true) @@ -931,7 +931,7 @@ class FormHelperTest < ActionView::TestCase end def test_text_area_placeholder_with_locales_fallback_and_nested_attributes - with_locale :placeholder do + I18n.with_locale :placeholder do form_for(@post, html: { id: "create-post" }) do |f| f.fields_for(:tags) do |cf| concat cf.text_area(:value, placeholder: true) @@ -2260,7 +2260,7 @@ class FormHelperTest < ActionView::TestCase end def test_submit_with_object_as_new_record_and_locale_strings - with_locale :submit do + I18n.with_locale :submit do @post.persisted = false @post.stub(:to_key, nil) do form_for(@post) do |f| @@ -2277,7 +2277,7 @@ class FormHelperTest < ActionView::TestCase end def test_submit_with_object_as_existing_record_and_locale_strings - with_locale :submit do + I18n.with_locale :submit do form_for(@post) do |f| concat f.submit end @@ -2291,7 +2291,7 @@ class FormHelperTest < ActionView::TestCase end def test_submit_without_object_and_locale_strings - with_locale :submit do + I18n.with_locale :submit do form_for(:post) do |f| concat f.submit class: "extra" end @@ -2305,7 +2305,7 @@ class FormHelperTest < ActionView::TestCase end def test_submit_with_object_which_is_overwritten_by_as_option - with_locale :submit do + I18n.with_locale :submit do form_for(@post, as: :another_post) do |f| concat f.submit end @@ -2320,7 +2320,7 @@ class FormHelperTest < ActionView::TestCase def test_submit_with_object_which_is_namespaced blog_post = Blog::Post.new("And his name will be forty and four.", 44) - with_locale :submit do + I18n.with_locale :submit do form_for(blog_post) do |f| concat f.submit end @@ -3554,7 +3554,6 @@ class FormHelperTest < ActionView::TestCase end private - def hidden_fields(options = {}) method = options[:method] @@ -3593,13 +3592,6 @@ class FormHelperTest < ActionView::TestCase false end - def with_locale(testing_locale = :label) - old_locale, I18n.locale = I18n.locale, testing_locale - yield - ensure - I18n.locale = old_locale - end - def with_default_enforce_utf8(value) old_value = ActionView::Helpers::FormTagHelper.default_enforce_utf8 ActionView::Helpers::FormTagHelper.default_enforce_utf8 = value diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb index 4ccd3ae336..c9c36917d6 100644 --- a/actionview/test/template/form_options_helper_test.rb +++ b/actionview/test/template/form_options_helper_test.rb @@ -29,10 +29,20 @@ class FormOptionsHelperTest < ActionView::TestCase end Continent = Struct.new("Continent", :continent_name, :countries) Country = Struct.new("Country", :country_id, :country_name) - Firm = Struct.new("Firm", :time_zone) Album = Struct.new("Album", :id, :title, :genre) end + class Firm + include ActiveModel::Validations + extend ActiveModel::Naming + + attr_accessor :time_zone + + def initialize(time_zone = nil) + @time_zone = time_zone + end + end + module FakeZones FakeZone = Struct.new(:name) do def to_s; name; end @@ -1294,7 +1304,7 @@ class FormOptionsHelperTest < ActionView::TestCase def test_time_zone_select_with_priority_zones_and_errors @firm = Firm.new("D") @firm.extend ActiveModel::Validations - @firm.errors[:time_zone] << "invalid" + assert_deprecated { @firm.errors[:time_zone] << "invalid" } zones = [ ActiveSupport::TimeZone.new("A"), ActiveSupport::TimeZone.new("D") ] html = time_zone_select("firm", "time_zone", zones) assert_dom_equal "<div class=\"field_with_errors\">" \ diff --git a/actionview/test/template/html_test.rb b/actionview/test/template/html_test.rb index 5cdff74d60..17f21cbbc5 100644 --- a/actionview/test/template/html_test.rb +++ b/actionview/test/template/html_test.rb @@ -4,16 +4,16 @@ 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 + assert_equal :html, ActionView::Template::HTML.new("", :html).format end test "formats returns string for recognized MIME type when MIME does not have symbol" do - foo = Mime::Type.lookup("foo") + foo = Mime::Type.lookup("text/foo") assert_nil foo.to_sym - assert_equal ["foo"], ActionView::Template::HTML.new("", foo).formats + assert_equal "text/foo", ActionView::Template::HTML.new("", foo).format end test "formats returns string for unknown MIME type" do - assert_equal ["foo"], ActionView::Template::HTML.new("", "foo").formats + assert_equal "foo", ActionView::Template::HTML.new("", "foo").format end end diff --git a/actionview/test/template/javascript_helper_test.rb b/actionview/test/template/javascript_helper_test.rb index 4c28aeaee1..f974e5ae0c 100644 --- a/actionview/test/template/javascript_helper_test.rb +++ b/actionview/test/template/javascript_helper_test.rb @@ -54,7 +54,7 @@ class JavaScriptHelperTest < ActionView::TestCase assert_equal "foo", output_buffer, "javascript_tag without a block should not concat to output_buffer" end - # Setting the :extname option will control what extension (if any) is appended to the url for assets + # Setting the :extname option will control what extension (if any) is appended to the URL for assets def test_javascript_include_tag assert_dom_equal "<script src='/foo.js'></script>", javascript_include_tag("/foo") assert_dom_equal "<script src='/foo'></script>", javascript_include_tag("/foo", extname: false) diff --git a/actionview/test/template/log_subscriber_test.rb b/actionview/test/template/log_subscriber_test.rb index 83bb651ea3..8b160a7336 100644 --- a/actionview/test/template/log_subscriber_test.rb +++ b/actionview/test/template/log_subscriber_test.rb @@ -16,8 +16,7 @@ class AVLogSubscriberTest < ActiveSupport::TestCase view_paths = ActionController::Base.view_paths lookup_context = ActionView::LookupContext.new(view_paths, {}, ["test"]) - renderer = ActionView::Renderer.new(lookup_context) - @view = ActionView::Base.with_empty_template_cache.new(renderer, {}) + @view = ActionView::Base.with_empty_template_cache.new(lookup_context, {}) ActionView::LogSubscriber.attach_to :action_view @@ -52,9 +51,20 @@ class AVLogSubscriberTest < ActiveSupport::TestCase def @view.combined_fragment_cache_key(*); "ahoy `controller` dependency"; end end + def test_render_template_template + Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do + @view.render(template: "test/hello_world") + wait + + assert_equal 2, @logger.logged(:info).size + assert_match(/Rendering test\/hello_world\.erb/, @logger.logged(:info).first) + assert_match(/Rendered test\/hello_world\.erb/, @logger.logged(:info).last) + end + end + def test_render_file_template Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do - @view.render(file: "test/hello_world") + @view.render(file: "#{FIXTURE_LOAD_PATH}/test/hello_world.erb") wait assert_equal 2, @logger.logged(:info).size diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb index 290f832794..72e1f50fdf 100644 --- a/actionview/test/template/lookup_context_test.rb +++ b/actionview/test/template/lookup_context_test.rb @@ -17,6 +17,16 @@ class LookupContextTest < ActiveSupport::TestCase I18n.locale = :en end + test "rendered_format is deprecated" do + assert_deprecated do + @lookup_context.rendered_format = "foo" + end + + assert_deprecated do + assert_equal "foo", @lookup_context.rendered_format + end + end + test "allows to override default_formats with ActionView::Base.default_formats" do formats = ActionView::Base.default_formats ActionView::Base.default_formats = [:foo, :bar] @@ -57,7 +67,7 @@ class LookupContextTest < ActiveSupport::TestCase test "handles explicitly defined */* formats fallback to :js" do @lookup_context.formats = [:js, Mime::ALL] - assert_equal [:js, *Mime::SET.symbols], @lookup_context.formats + assert_equal [:js, *Mime::SET.symbols].uniq, @lookup_context.formats end test "adds :html fallback to :js formats" do @@ -65,6 +75,14 @@ class LookupContextTest < ActiveSupport::TestCase assert_equal [:js, :html], @lookup_context.formats end + test "raises on invalid format assignment" do + ex = assert_raises ArgumentError do + @lookup_context.formats = [:html, :invalid, "also bad"] + end + + assert_equal 'Invalid formats: :invalid, "also bad"', ex.message + end + test "provides getters and setters for locale" do @lookup_context.locale = :pt assert_equal :pt, @lookup_context.locale @@ -111,11 +129,11 @@ class LookupContextTest < ActiveSupport::TestCase assert_equal "Hello texty phone!", template.source end - test "found templates respects given formats if one cannot be found from template or handler" do + test "found templates have nil format if one cannot be found from template or handler" do assert_called(ActionView::Template::Handlers::Builder, :default_format, returns: nil) do @lookup_context.formats = [:text] template = @lookup_context.find("hello", %w(test)) - assert_equal [:text], template.formats + assert_nil template.format end end @@ -125,16 +143,16 @@ class LookupContextTest < ActiveSupport::TestCase assert_deprecated do @lookup_context.with_fallbacks do assert_equal 3, @lookup_context.view_paths.size - assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("") - assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("/") + assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.instances[0] + assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.instances[1] end end @lookup_context = @lookup_context.with_fallbacks assert_equal 3, @lookup_context.view_paths.size - assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("") - assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("/") + assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.instances[0] + assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.instances[1] end test "add fallbacks just once in nested fallbacks calls" do @@ -225,56 +243,6 @@ class LookupContextTest < ActiveSupport::TestCase end end -class LookupContextWithFalseCaching < ActiveSupport::TestCase - def setup - @resolver = ActionView::FixtureResolver.new("test/_foo.erb" => ["Foo", Time.utc(2000)]) - @lookup_context = ActionView::LookupContext.new(@resolver, {}) - end - - test "templates are always found in the resolver but timestamp is checked before being compiled" do - ActionView::Resolver.stub(:caching?, false) do - template = @lookup_context.find("foo", %w(test), true) - assert_equal "Foo", template.source - - # Now we are going to change the template, but it won't change the returned template - # since the timestamp is the same. - @resolver.data["test/_foo.erb"][0] = "Bar" - template = @lookup_context.find("foo", %w(test), true) - assert_equal "Foo", template.source - - # Now update the timestamp. - @resolver.data["test/_foo.erb"][1] = Time.now.utc - template = @lookup_context.find("foo", %w(test), true) - assert_equal "Bar", template.source - end - end - - test "if no template was found in the second lookup, with no cache, raise error" do - ActionView::Resolver.stub(:caching?, false) do - template = @lookup_context.find("foo", %w(test), true) - assert_equal "Foo", template.source - - @resolver.data.clear - assert_raise ActionView::MissingTemplate do - @lookup_context.find("foo", %w(test), true) - end - end - end - - test "if no template was cached in the first lookup, retrieval should work in the second call" do - ActionView::Resolver.stub(:caching?, false) do - @resolver.data.clear - assert_raise ActionView::MissingTemplate do - @lookup_context.find("foo", %w(test), true) - end - - @resolver.data["test/_foo.erb"] = ["Foo", Time.utc(2000)] - template = @lookup_context.find("foo", %w(test), true) - assert_equal "Foo", template.source - end - end -end - class TestMissingTemplate < ActiveSupport::TestCase def setup @lookup_context = ActionView::LookupContext.new("/Path/to/views", {}) diff --git a/actionview/test/template/optimized_file_system_resolver_test.rb b/actionview/test/template/optimized_file_system_resolver_test.rb new file mode 100644 index 0000000000..c0c64357ce --- /dev/null +++ b/actionview/test/template/optimized_file_system_resolver_test.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "template/resolver_shared_tests" + +class OptimizedFileSystemResolverTest < ActiveSupport::TestCase + include ResolverSharedTests + + def resolver + ActionView::OptimizedFileSystemResolver.new(tmpdir) + end +end diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index 3f298d81f3..2235a7816f 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -20,7 +20,10 @@ module RenderTestCases controller = TestController.new - @controller_view = controller.view_context_class.with_empty_template_cache.new(controller.view_renderer, controller.view_assigns, controller) + @controller_view = controller.view_context_class.with_empty_template_cache.new( + controller.lookup_context, + controller.view_assigns, + controller) # Reload and register danish language for testing I18n.backend.store_translations "da", {} @@ -30,30 +33,51 @@ module RenderTestCases assert_equal ORIGINAL_LOCALES, I18n.available_locales.map(&:to_s).sort end + def teardown + I18n.reload! + ActionController::Base.view_paths.map(&:clear_cache) + end + + def test_implicit_format_comes_from_parent_template + rendered_templates = JSON.parse(@controller_view.render(template: "test/mixing_formats")) + assert_equal({ "format" => "HTML", + "children" => ["XML", "HTML"] }, rendered_templates) + end + + def test_implicit_format_comes_from_parent_template_cascading + rendered_templates = JSON.parse(@controller_view.render(template: "test/mixing_formats_deep")) + assert_equal({ "format" => "HTML", + "children" => [ + { "format" => "XML", "children" => ["XML"] }, + { "format" => "HTML", "children" => ["HTML"] }, + ] }, rendered_templates) + end + def test_render_without_options e = assert_raises(ArgumentError) { @view.render() } assert_match(/You invoked render but did not give any of (.+) option\./, e.message) end + def test_render_template + assert_equal "Hello world!", @view.render(template: "test/hello_world") + end + + def test_render_file - assert_equal "Hello world!", @view.render(file: "test/hello_world") + assert_equal "Hello world!", assert_deprecated { @view.render(file: "test/hello_world") } end # Test if :formats, :locale etc. options are passed correctly to the resolvers. def test_render_file_with_format - assert_match "<h1>No Comment</h1>", @view.render(file: "comments/empty", formats: [:html]) - assert_match "<error>No Comment</error>", @view.render(file: "comments/empty", formats: [:xml]) - assert_match "<error>No Comment</error>", @view.render(file: "comments/empty", formats: :xml) + assert_match "<h1>No Comment</h1>", assert_deprecated { @view.render(file: "comments/empty", formats: [:html]) } + assert_match "<error>No Comment</error>", assert_deprecated { @view.render(file: "comments/empty", formats: [:xml]) } + assert_match "<error>No Comment</error>", assert_deprecated { @view.render(file: "comments/empty", formats: :xml) } end def test_render_template_with_format assert_match "<h1>No Comment</h1>", @view.render(template: "comments/empty", formats: [:html]) assert_match "<error>No Comment</error>", @view.render(template: "comments/empty", formats: [:xml]) - end - - def test_rendered_format_without_format - @view.render(inline: "test") - assert_equal :html, @view.lookup_context.rendered_format + assert_match "<error>No Comment</error>", @view.render(template: "comments/empty", formats: :xml) end def test_render_partial_implicitly_use_format_of_the_rendered_template @@ -68,7 +92,7 @@ module RenderTestCases def test_render_partial_use_last_prepended_format_for_partials_with_the_same_names @view.lookup_context.formats = [:html] - assert_equal "\nHTML Template, but JSON partial", @view.render(template: "test/change_priority") + assert_equal "\nHTML Template, but HTML partial", @view.render(template: "test/change_priority") end def test_render_template_with_a_missing_partial_of_another_format @@ -80,8 +104,8 @@ module RenderTestCases end def test_render_file_with_locale - assert_equal "<h1>Kein Kommentar</h1>", @view.render(file: "comments/empty", locale: [:de]) - assert_equal "<h1>Kein Kommentar</h1>", @view.render(file: "comments/empty", locale: :de) + assert_equal "<h1>Kein Kommentar</h1>", assert_deprecated { @view.render(file: "comments/empty", locale: [:de]) } + assert_equal "<h1>Kein Kommentar</h1>", assert_deprecated { @view.render(file: "comments/empty", locale: :de) } end def test_render_template_with_locale @@ -93,8 +117,8 @@ module RenderTestCases end def test_render_file_with_handlers - assert_equal "<h1>No Comment</h1>\n", @view.render(file: "comments/empty", handlers: [:builder]) - assert_equal "<h1>No Comment</h1>\n", @view.render(file: "comments/empty", handlers: :builder) + assert_equal "<h1>No Comment</h1>\n", assert_deprecated { @view.render(file: "comments/empty", handlers: [:builder]) } + assert_equal "<h1>No Comment</h1>\n", assert_deprecated { @view.render(file: "comments/empty", handlers: :builder) } end def test_render_template_with_handlers @@ -111,7 +135,7 @@ module RenderTestCases def test_render_raw_is_html_safe_and_does_not_escape_output buffer = ActiveSupport::SafeBuffer.new - buffer << @view.render(file: "plain_text") + buffer << @view.render(template: "plain_text") assert_equal true, buffer.html_safe? assert_equal buffer, "<%= hello_world %>\n" end @@ -124,40 +148,45 @@ module RenderTestCases assert_equal "4", @view.render(inline: "(2**2).to_s", type: :ruby) end - def test_render_file_with_localization_on_context_level + def test_render_template_with_localization_on_context_level old_locale, @view.locale = @view.locale, :da - assert_equal "Hey verden", @view.render(file: "test/hello_world") + assert_equal "Hey verden", @view.render(template: "test/hello_world") ensure @view.locale = old_locale end - def test_render_file_with_dashed_locale + def test_render_template_with_dashed_locale old_locale, @view.locale = @view.locale, :"pt-BR" - assert_equal "Ola mundo", @view.render(file: "test/hello_world") + assert_equal "Ola mundo", @view.render(template: "test/hello_world") ensure @view.locale = old_locale end - def test_render_file_at_top_level - assert_equal "Elastica", @view.render(file: "/shared") + def test_render_template_at_top_level + assert_equal "Elastica", @view.render(template: "/shared") end - def test_render_file_with_full_path + def test_render_file_with_full_path_no_extension template_path = File.expand_path("../fixtures/test/hello_world", __dir__) + assert_equal "Hello world!", assert_deprecated { @view.render(file: template_path) } + end + + def test_render_file_with_full_path + template_path = File.expand_path("../fixtures/test/hello_world.erb", __dir__) assert_equal "Hello world!", @view.render(file: template_path) end def test_render_file_with_instance_variables - assert_equal "The secret is in the sauce\n", @view.render(file: "test/render_file_with_ivar") + assert_equal "The secret is in the sauce\n", assert_deprecated { @view.render(file: "test/render_file_with_ivar") } end def test_render_file_with_locals locals = { secret: "in the sauce" } - assert_equal "The secret is in the sauce\n", @view.render(file: "test/render_file_with_locals", locals: locals) + assert_equal "The secret is in the sauce\n", assert_deprecated { @view.render(file: "test/render_file_with_locals", locals: locals) } end def test_render_file_not_using_full_path_with_dot_in_path - assert_equal "The secret is in the sauce\n", @view.render(file: "test/dot.directory/render_file_with_ivar") + assert_equal "The secret is in the sauce\n", assert_deprecated { @view.render(file: "test/dot.directory/render_file_with_ivar") } end def test_render_partial_from_default @@ -245,18 +274,24 @@ module RenderTestCases "and is followed by any combination of letters, numbers and underscores.", e.message end + def test_render_template_with_syntax_error + e = assert_raises(ActionView::Template::Error) { @view.render(template: "test/syntax_error") } + assert_match %r!syntax!, e.message + assert_equal "1: <%= foo(", e.annotated_source_code[0].strip + end + def test_render_partial_with_errors e = assert_raises(ActionView::Template::Error) { @view.render(partial: "test/raise") } assert_match %r!method.*doesnt_exist!, e.message assert_equal "", e.sub_template_message assert_equal "1", e.line_number - assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code[0].strip + assert_equal "1: <%= doesnt_exist %>", e.annotated_source_code[0].strip assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name end def test_render_error_indentation e = assert_raises(ActionView::Template::Error) { @view.render(partial: "test/raise_indentation") } - error_lines = e.annoted_source_code + error_lines = e.annotated_source_code assert_match %r!error\shere!, e.message assert_equal "11", e.line_number assert_equal " 9: <p>Ninth paragraph</p>", error_lines.second @@ -272,11 +307,11 @@ module RenderTestCases end def test_render_file_with_errors - e = assert_raises(ActionView::Template::Error) { @view.render(file: File.expand_path("test/_raise", FIXTURE_LOAD_PATH)) } + e = assert_raises(ActionView::Template::Error) { assert_deprecated { @view.render(file: File.expand_path("test/_raise", FIXTURE_LOAD_PATH)) } } assert_match %r!method.*doesnt_exist!, e.message assert_equal "", e.sub_template_message assert_equal "1", e.line_number - assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code[0].strip + assert_equal "1: <%= doesnt_exist %>", e.annotated_source_code[0].strip assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name end @@ -347,6 +382,17 @@ module RenderTestCases assert_deprecated do ActionView::Base.new ["/a"] end + + assert_deprecated do + ActionView::Base.new ActionView::PathSet.new ["/a"] + end + end + + def test_without_compiled_method_container_is_deprecated + view = ActionView::Base.with_view_paths(ActionController::Base.view_paths) + assert_deprecated("ActionView::Base instances must implement `compiled_method_container`") do + assert_equal "Hello world!", view.render(template: "test/hello_world") + end end def test_render_partial_without_object_does_not_put_partial_name_to_local_assigns @@ -525,28 +571,28 @@ module RenderTestCases def test_render_ignores_templates_with_malformed_template_handlers %w(malformed malformed.erb malformed.html.erb malformed.en.html.erb).each do |name| 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}") } + assert_raises(ActionView::MissingTemplate) { @view.render(template: "test/malformed/#{name}") } end end def test_render_with_layout assert_equal %(<title></title>\nHello world!\n), - @view.render(file: "test/hello_world", layout: "layouts/yield") + @view.render(template: "test/hello_world", layout: "layouts/yield") end def test_render_with_layout_which_has_render_inline assert_equal %(welcome\nHello world!\n), - @view.render(file: "test/hello_world", layout: "layouts/yield_with_render_inline_inside") + @view.render(template: "test/hello_world", layout: "layouts/yield_with_render_inline_inside") end def test_render_with_layout_which_renders_another_partial assert_equal %(partial html\nHello world!\n), - @view.render(file: "test/hello_world", layout: "layouts/yield_with_render_partial_inside") + @view.render(template: "test/hello_world", layout: "layouts/yield_with_render_partial_inside") end def test_render_partial_with_html_only_extension assert_equal %(<h1>partial html</h1>\nHello world!\n), - @view.render(file: "test/hello_world", layout: "layouts/render_partial_html") + @view.render(template: "test/hello_world", layout: "layouts/render_partial_html") end def test_render_layout_with_block_and_yield @@ -601,17 +647,17 @@ module RenderTestCases def test_render_with_nested_layout assert_equal %(<title>title</title>\n\n<div id="column">column</div>\n<div id="content">content</div>\n), - @view.render(file: "test/nested_layout", layout: "layouts/yield") + @view.render(template: "test/nested_layout", layout: "layouts/yield") end def test_render_with_file_in_layout assert_equal %(\n<title>title</title>\n\n), - @view.render(file: "test/layout_render_file") + @view.render(template: "test/layout_render_file") end def test_render_layout_with_object assert_equal %(<title>David</title>), - @view.render(file: "test/layout_render_object") + @view.render(template: "test/layout_render_object") end def test_render_with_passing_couple_extensions_to_one_register_template_handler_function_call @@ -636,11 +682,6 @@ class CachedViewRenderTest < ActiveSupport::TestCase assert_equal ActionView::OptimizedFileSystemResolver, view_paths.first.class setup_view(view_paths) end - - def teardown - GC.start - I18n.reload! - end end class LazyViewRenderTest < ActiveSupport::TestCase @@ -656,14 +697,9 @@ class LazyViewRenderTest < ActiveSupport::TestCase setup_view(view_paths) end - def teardown - GC.start - I18n.reload! - end - def test_render_utf8_template_with_magic_comment with_external_encoding Encoding::ASCII_8BIT do - result = @view.render(file: "test/utf8_magic", formats: [:html], layouts: "layouts/yield") + result = @view.render(template: "test/utf8_magic", formats: [:html], layouts: "layouts/yield") assert_equal Encoding::UTF_8, result.encoding assert_equal "\nРусский \nтекст\n\nUTF-8\nUTF-8\nUTF-8\n", result end @@ -671,7 +707,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase def test_render_utf8_template_with_default_external_encoding with_external_encoding Encoding::UTF_8 do - result = @view.render(file: "test/utf8", formats: [:html], layouts: "layouts/yield") + result = @view.render(template: "test/utf8", formats: [:html], layouts: "layouts/yield") assert_equal Encoding::UTF_8, result.encoding assert_equal "Русский текст\n\nUTF-8\nUTF-8\nUTF-8\n", result end @@ -679,14 +715,14 @@ class LazyViewRenderTest < ActiveSupport::TestCase def test_render_utf8_template_with_incompatible_external_encoding with_external_encoding Encoding::SHIFT_JIS do - e = assert_raises(ActionView::Template::Error) { @view.render(file: "test/utf8", formats: [:html], layouts: "layouts/yield") } + e = assert_raises(ActionView::Template::Error) { @view.render(template: "test/utf8", formats: [:html], layouts: "layouts/yield") } assert_match "Your template was not saved as valid Shift_JIS", e.cause.message end end def test_render_utf8_template_with_partial_with_incompatible_encoding with_external_encoding Encoding::SHIFT_JIS do - e = assert_raises(ActionView::Template::Error) { @view.render(file: "test/utf8_magic_with_bare_partial", formats: [:html], layouts: "layouts/yield") } + e = assert_raises(ActionView::Template::Error) { @view.render(template: "test/utf8_magic_with_bare_partial", formats: [:html], layouts: "layouts/yield") } assert_match "Your template was not saved as valid Shift_JIS", e.cause.message end end @@ -717,9 +753,12 @@ class CachedCollectionViewRenderTest < ActiveSupport::TestCase setup_view(view_paths) end - teardown do - GC.start - I18n.reload! + test "template body written to cache" do + customer = Customer.new("david", 1) + key = cache_key(customer, "test/_customer") + assert_nil ActionView::PartialRenderer.collection_cache.read(key) + @view.render(partial: "test/customer", collection: [customer], cached: true) + assert_equal "Hello: david", ActionView::PartialRenderer.collection_cache.read(key) end test "collection caching does not cache by default" do @@ -752,9 +791,42 @@ class CachedCollectionViewRenderTest < ActiveSupport::TestCase @view.render(partial: "test/cached_customer", collection: [customer], cached: true) end + test "collection caching does not work on multi-partials" do + a = Object.new + b = Object.new + def a.to_partial_path; "test/partial_iteration_1"; end + def b.to_partial_path; "test/partial_iteration_2"; end + + assert_raises(NotImplementedError) do + @controller_view.render(partial: [a, b], cached: true) + end + end + + test "collection caching with repeated collection" do + sets = [ + [1, 2, 3, 4, 5], + [1, 2, 3, 4, 4], + [1, 2, 3, 4, 5], + [1, 2, 3, 4, 4], + [1, 2, 3, 4, 6] + ] + + result = @view.render(partial: "test/cached_set", collection: sets, cached: true) + + splited_result = result.split("\n") + assert_equal 5, splited_result.count + assert_equal [ + "1 | 2 | 3 | 4 | 5", + "1 | 2 | 3 | 4 | 4", + "1 | 2 | 3 | 4 | 5", + "1 | 2 | 3 | 4 | 4", + "1 | 2 | 3 | 4 | 6" + ], splited_result + end + private def cache_key(*names, virtual_path) - digest = ActionView::Digestor.digest name: virtual_path, finder: @view.lookup_context, dependencies: [] + digest = ActionView::Digestor.digest name: virtual_path, format: :html, finder: @view.lookup_context, dependencies: [] @view.combined_fragment_cache_key([ "#{virtual_path}:#{digest}", *names ]) end end diff --git a/actionview/test/template/resolver_patterns_test.rb b/actionview/test/template/resolver_patterns_test.rb index 1e1a4c5063..22815c8dbe 100644 --- a/actionview/test/template/resolver_patterns_test.rb +++ b/actionview/test/template/resolver_patterns_test.rb @@ -6,7 +6,10 @@ class ResolverPatternsTest < ActiveSupport::TestCase def setup path = File.expand_path("../fixtures", __dir__) pattern = ":prefix/{:formats/,}:action{.:formats,}{+:variants,}{.:handlers,}" - @resolver = ActionView::FileSystemResolver.new(path, pattern) + + assert_deprecated do + @resolver = ActionView::FileSystemResolver.new(path, pattern) + end end def test_should_return_empty_list_for_unknown_path @@ -19,7 +22,7 @@ class ResolverPatternsTest < ActiveSupport::TestCase assert_equal 1, templates.size, "expected one template" assert_equal "Hello custom patterns!", templates.first.source assert_equal "custom_pattern/path", templates.first.virtual_path - assert_equal [:html], templates.first.formats + assert_nil templates.first.format end def test_should_return_all_templates_when_ambiguous_pattern diff --git a/actionview/test/template/resolver_shared_tests.rb b/actionview/test/template/resolver_shared_tests.rb new file mode 100644 index 0000000000..8b47c5bc89 --- /dev/null +++ b/actionview/test/template/resolver_shared_tests.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +module ResolverSharedTests + attr_reader :tmpdir + + def run(*args) + capture_exceptions do + Dir.mktmpdir(nil, __dir__) { |dir| @tmpdir = dir; super } + end + end + + def with_file(filename, source = "File at #{filename}") + path = File.join(tmpdir, filename) + FileUtils.mkdir_p(File.dirname(path)) + File.write(path, source) + end + + def context + @context ||= ActionView::LookupContext.new(resolver) + end + + def test_can_find_with_no_extensions + with_file "test/hello_world", "Hello default!" + + templates = resolver.find_all("hello_world", "test", false, locale: [:en], formats: [:html], variants: [:phone], handlers: [:erb]) + assert_equal 1, templates.size + assert_equal "Hello default!", templates[0].source + assert_equal "test/hello_world", templates[0].virtual_path + assert_nil templates[0].format + assert_nil templates[0].variant + assert_kind_of ActionView::Template::Handlers::Raw, templates[0].handler + end + + def test_can_find_with_just_handler + with_file "test/hello_world.erb", "Hello erb!" + + templates = resolver.find_all("hello_world", "test", false, locale: [:en], formats: [:html], variants: [:phone], handlers: [:erb]) + assert_equal 1, templates.size + assert_equal "Hello erb!", templates[0].source + assert_equal "test/hello_world", templates[0].virtual_path + assert_nil templates[0].format + assert_nil templates[0].variant + assert_kind_of ActionView::Template::Handlers::ERB, templates[0].handler + end + + def test_can_find_with_format_and_handler + with_file "test/hello_world.text.builder", "Hello plain text!" + + templates = resolver.find_all("hello_world", "test", false, locale: [:en], formats: [:html, :text], variants: [:phone], handlers: [:erb, :builder]) + assert_equal 1, templates.size + assert_equal "Hello plain text!", templates[0].source + assert_equal "test/hello_world", templates[0].virtual_path + assert_equal :text, templates[0].format + assert_nil templates[0].variant + assert_kind_of ActionView::Template::Handlers::Builder, templates[0].handler + end + + def test_can_find_with_variant_format_and_handler + with_file "test/hello_world.html+phone.erb", "Hello plain text!" + + templates = resolver.find_all("hello_world", "test", false, locale: [:en], formats: [:html], variants: [:phone], handlers: [:erb]) + assert_equal 1, templates.size + assert_equal "Hello plain text!", templates[0].source + assert_equal "test/hello_world", templates[0].virtual_path + assert_equal :html, templates[0].format + assert_equal "phone", templates[0].variant + assert_kind_of ActionView::Template::Handlers::ERB, templates[0].handler + end + + def test_can_find_with_any_variant_format_and_handler + with_file "test/hello_world.html+phone.erb", "Hello plain text!" + + templates = resolver.find_all("hello_world", "test", false, locale: [:en], formats: [:html], variants: :any, handlers: [:erb]) + assert_equal 1, templates.size + assert_equal "Hello plain text!", templates[0].source + assert_equal "test/hello_world", templates[0].virtual_path + assert_equal :html, templates[0].format + assert_equal "phone", templates[0].variant + assert_kind_of ActionView::Template::Handlers::ERB, templates[0].handler + end + + def test_doesnt_find_template_with_wrong_details + with_file "test/hello_world.html.erb", "Hello plain text!" + + templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:xml], variants: :any, handlers: [:builder]) + assert_equal 0, templates.size + + templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:xml], variants: :any, handlers: [:erb]) + assert_equal 0, templates.size + end + + def test_found_template_is_cached + with_file "test/hello_world.html.erb", "Hello HTML!" + + a = context.find("hello_world", "test", false, [], {}) + b = context.find("hello_world", "test", false, [], {}) + assert_same a, b + end + + def test_different_templates_when_cache_disabled + with_file "test/hello_world.html.erb", "Hello HTML!" + + a = context.find("hello_world", "test", false, [], {}) + b = context.disable_cache { context.find("hello_world", "test", false, [], {}) } + c = context.find("hello_world", "test", false, [], {}) + + # disable_cache should give us a new object + assert_not_same a, b + + # but it should not clear the cache + assert_same a, c + end + + def test_same_template_from_different_details_is_same_object + with_file "test/hello_world.html.erb", "Hello HTML!" + + a = context.find("hello_world", "test", false, [], locale: [:en]) + b = context.find("hello_world", "test", false, [], locale: [:fr]) + assert_same a, b + end + + def test_templates_with_optional_locale_shares_common_object + with_file "test/hello_world.text.erb", "Generic plain text!" + with_file "test/hello_world.fr.text.erb", "Texte en Francais!" + + en = context.find_all("hello_world", "test", false, [], locale: [:en]) + fr = context.find_all("hello_world", "test", false, [], locale: [:fr]) + + assert_equal 1, en.size + assert_equal 2, fr.size + + assert_equal "Generic plain text!", en[0].source + assert_equal "Texte en Francais!", fr[0].source + assert_equal "Generic plain text!", fr[1].source + + assert_same en[0], fr[1] + end + + def test_virtual_path_is_preserved_with_dot + with_file "test/hello_world.html.erb", "Hello html!" + + template = context.find("hello_world.html", "test", false, [], {}) + assert_equal "test/hello_world.html", template.virtual_path + + template = context.find("hello_world", "test", false, [], {}) + assert_equal "test/hello_world", template.virtual_path + end +end diff --git a/actionview/test/template/streaming_render_test.rb b/actionview/test/template/streaming_render_test.rb index a5b59a700e..a5e673e71e 100644 --- a/actionview/test/template/streaming_render_test.rb +++ b/actionview/test/template/streaming_render_test.rb @@ -47,12 +47,12 @@ class FiberedTest < SetupFiberedBase end def test_render_file - assert_equal "Hello world!", buffered_render(file: "test/hello_world") + assert_equal "Hello world!", assert_deprecated { buffered_render(file: "test/hello_world") } end def test_render_file_with_locals locals = { secret: "in the sauce" } - assert_equal "The secret is in the sauce\n", buffered_render(file: "test/render_file_with_locals", locals: locals) + assert_equal "The secret is in the sauce\n", assert_deprecated { buffered_render(file: "test/render_file_with_locals", locals: locals) } end def test_render_partial diff --git a/actionview/test/template/template_test.rb b/actionview/test/template/template_test.rb index a069c8f2d0..049e0bc129 100644 --- a/actionview/test/template/template_test.rb +++ b/actionview/test/template/template_test.rb @@ -38,7 +38,9 @@ class TestERBTemplate < ActiveSupport::TestCase "<%= @virtual_path %>", "partial", ERBHandler, - virtual_path: "partial" + virtual_path: "partial", + format: :html, + locals: [] ) end @@ -55,8 +57,9 @@ class TestERBTemplate < ActiveSupport::TestCase end end - def new_template(body = "<%= hello %>", details = { format: :html }) - ActionView::Template.new(body.dup, "hello template", details.fetch(:handler) { ERBHandler }, { virtual_path: "hello" }.merge!(details)) + def new_template(body = "<%= hello %>", details = {}) + details = { format: :html, locals: [] }.merge details + ActionView::Template.new(body.dup, "hello template", details.delete(:handler) || ERBHandler, { virtual_path: "hello" }.merge!(details)) end def render(locals = {}) @@ -88,10 +91,10 @@ class TestERBTemplate < ActiveSupport::TestCase assert_equal "<%= hello %>", render end - def test_template_loses_its_source_after_rendering + def test_template_does_not_lose_its_source_after_rendering @template = new_template render - assert_nil @template.source + assert_equal "<%= hello %>", @template.source end def test_template_does_not_lose_its_source_after_rendering_if_it_does_not_have_a_virtual_path @@ -101,8 +104,7 @@ class TestERBTemplate < ActiveSupport::TestCase end def test_locals - @template = new_template("<%= my_local %>") - @template.locals = [:my_local] + @template = new_template("<%= my_local %>", locals: [:my_local]) assert_equal "I am a local", render(my_local: "I am a local") end @@ -119,26 +121,10 @@ class TestERBTemplate < ActiveSupport::TestCase assert_equal "hellopartialhello", render end - def test_refresh_with_templates - @template = new_template("Hello", virtual_path: "test/foo/bar") - @template.locals = [:key] - assert_called_with(@context.lookup_context, :find_template, ["bar", %w(test/foo), false, [:key]], returns: "template") do - assert_equal "template", @template.refresh(@context) - end - end - - def test_refresh_with_partials - @template = new_template("Hello", virtual_path: "test/_foo") - @template.locals = [:key] - assert_called_with(@context.lookup_context, :find_template, ["foo", %w(test), true, [:key]], returns: "partial") do - assert_equal "partial", @template.refresh(@context) - end - end - - def test_refresh_raises_an_error_without_virtual_path - @template = new_template("Hello", virtual_path: nil) - assert_raise RuntimeError do - @template.refresh(@context) + def test_refresh_is_deprecated + @template = new_template("Hello", virtual_path: "test/foo/bar", locals: [:key]) + assert_deprecated do + assert_same @template, @template.refresh(@context) end end @@ -213,4 +199,14 @@ class TestERBTemplate < ActiveSupport::TestCase ensure silence_warnings { Encoding.default_external = old } end + + def test_short_identifier + @template = new_template("hello") + assert_equal "hello template", @template.short_identifier + end + + def test_template_inspect + @template = new_template("hello") + assert_equal "#<ActionView::Template hello template locals=[]>", @template.inspect + end end diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb index c89aff9c9d..0b2a2a9911 100644 --- a/actionview/test/template/test_case_test.rb +++ b/actionview/test/template/test_case_test.rb @@ -284,7 +284,7 @@ module ActionView @controller.controller_path = "test" @customers = [DeveloperStruct.new("Eloy"), DeveloperStruct.new("Manfred")] - assert_match(/Hello: EloyHello: Manfred/, render(file: "test/list")) + assert_match(/Hello: EloyHello: Manfred/, render(template: "test/list")) end test "is able to render partials from templates and also use instance variables after view has been referenced" do @@ -293,7 +293,7 @@ module ActionView view @customers = [DeveloperStruct.new("Eloy"), DeveloperStruct.new("Manfred")] - assert_match(/Hello: EloyHello: Manfred/, render(file: "test/list")) + assert_match(/Hello: EloyHello: Manfred/, render(template: "test/list")) end test "is able to use helpers that depend on the view flow" do diff --git a/actionview/test/template/testing/fixture_resolver_test.rb b/actionview/test/template/testing/fixture_resolver_test.rb index 9954e3500d..6d0317e0c9 100644 --- a/actionview/test/template/testing/fixture_resolver_test.rb +++ b/actionview/test/template/testing/fixture_resolver_test.rb @@ -15,6 +15,16 @@ class FixtureResolverTest < ActiveSupport::TestCase assert_equal 1, templates.size, "expected one template" assert_equal "this text", templates.first.source assert_equal "arbitrary/path", templates.first.virtual_path - assert_equal [:html], templates.first.formats + assert_nil templates.first.format + end + + def test_should_match_templates_with_variants + resolver = ActionView::FixtureResolver.new("arbitrary/path.html+variant.erb" => "this text") + templates = resolver.find_all("path", "arbitrary", false, locale: [], formats: [:html], variants: [:variant], 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 + assert_equal :html, templates.first.format + assert_equal "variant", templates.first.variant end end diff --git a/actionview/test/template/testing/null_resolver_test.rb b/actionview/test/template/testing/null_resolver_test.rb index 53364c1d90..c7c78804c0 100644 --- a/actionview/test/template/testing/null_resolver_test.rb +++ b/actionview/test/template/testing/null_resolver_test.rb @@ -9,6 +9,6 @@ class NullResolverTest < ActiveSupport::TestCase assert_equal 1, templates.size, "expected one template" assert_equal "Template generated by Null Resolver", templates.first.source assert_equal "arbitrary/path.erb", templates.first.virtual_path.to_s - assert_equal [:html], templates.first.formats + assert_nil templates.first.format end end diff --git a/actionview/test/template/text_test.rb b/actionview/test/template/text_test.rb index 0c6470df21..c837c53587 100644 --- a/actionview/test/template/text_test.rb +++ b/actionview/test/template/text_test.rb @@ -3,8 +3,8 @@ require "abstract_unit" class TextTest < ActiveSupport::TestCase - test "formats always return :text" do - assert_equal [:text], ActionView::Template::Text.new("").formats + test "format always return :text" do + assert_equal :text, ActionView::Template::Text.new("").format end test "identifier should return 'text template'" do diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb index 23fc9850c4..9afdc3c68f 100644 --- a/actionview/test/template/translation_helper_test.rb +++ b/actionview/test/template/translation_helper_test.rb @@ -127,20 +127,20 @@ class TranslationHelperTest < ActiveSupport::TestCase end def test_finds_translation_scoped_by_partial - assert_equal "Foo", view.render(file: "translations/templates/found").strip + assert_equal "Foo", view.render(template: "translations/templates/found").strip end def test_finds_array_of_translations_scoped_by_partial - assert_equal "Foo Bar", @view.render(file: "translations/templates/array").strip + assert_equal "Foo Bar", @view.render(template: "translations/templates/array").strip end def test_default_lookup_scoped_by_partial - assert_equal "Foo", view.render(file: "translations/templates/default").strip + assert_equal "Foo", view.render(template: "translations/templates/default").strip end def test_missing_translation_scoped_by_partial expected = '<span class="translation_missing" title="translation missing: en.translations.templates.missing.missing">Missing</span>' - assert_equal expected, view.render(file: "translations/templates/missing").strip + assert_equal expected, view.render(template: "translations/templates/missing").strip end def test_translate_does_not_mark_plain_text_as_safe_html diff --git a/actionview/test/ujs/public/test/call-remote.js b/actionview/test/ujs/public/test/call-remote.js index 778dc1b09a..fb033491f9 100644 --- a/actionview/test/ujs/public/test/call-remote.js +++ b/actionview/test/ujs/public/test/call-remote.js @@ -53,7 +53,7 @@ asyncTest('form default method is GET', 1, function() { }) }) -asyncTest('form url is picked up from "action"', 1, function() { +asyncTest('form URL is picked up from "action"', 1, function() { buildForm({ method: 'post' }) submit(function(e, data, status, xhr) { @@ -61,7 +61,7 @@ asyncTest('form url is picked up from "action"', 1, function() { }) }) -asyncTest('form url is read from "action" not "href"', 1, function() { +asyncTest('form URL is read from "action" not "href"', 1, function() { buildForm({ method: 'post', href: '/echo2' }) submit(function(e, data, status, xhr) { @@ -69,7 +69,7 @@ asyncTest('form url is read from "action" not "href"', 1, function() { }) }) -asyncTest('form url is read from submit button "formaction" if submit is triggered by that button', 1, function() { +asyncTest('form URL is read from submit button "formaction" if submit is triggered by that button', 1, function() { var submitButton = $('<input type="submit" formaction="/echo">') buildForm({ method: 'post', href: '/echo2' }) @@ -128,14 +128,14 @@ asyncTest('execution of JS code does not modify current DOM', 1, function() { }) }) -asyncTest('HTML content should be plain-text', 1, function() { +asyncTest('HTML document should be parsed', 1, function() { buildForm({ method: 'post', 'data-type': 'html' }) $('form').append('<input type="text" name="content_type" value="text/html">') $('form').append('<input type="text" name="content" value="<p>hello</p>">') submit(function(e, data, status, xhr) { - ok(data === '<p>hello</p>', 'returned data should be a plain-text string') + ok(data instanceof HTMLDocument, 'returned data should be an HTML document') }) }) diff --git a/actionview/test/ujs/public/test/data-remote.js b/actionview/test/ujs/public/test/data-remote.js index 55d39b0a52..9e41067549 100644 --- a/actionview/test/ujs/public/test/data-remote.js +++ b/actionview/test/ujs/public/test/data-remote.js @@ -121,7 +121,7 @@ asyncTest('clicking on a link with both query string in href and data-params', 4 App.assertGetRequest(data) equal(data.params.data1, 'value1', 'ajax arguments should have key data1 with right value') equal(data.params.data2, 'value2', 'ajax arguments should have key data2 with right value') - equal(data.params.data3, 'value3', 'query string in url should be passed to server with right value') + equal(data.params.data3, 'value3', 'query string in URL should be passed to server with right value') }) .bindNative('ajax:complete', function() { start() }) .triggerNative('click') @@ -135,7 +135,7 @@ asyncTest('clicking on a link with both query string in href and data-params wit App.assertPostRequest(data) equal(data.params.data1, 'value1', 'ajax arguments should have key data1 with right value') equal(data.params.data2, 'value2', 'ajax arguments should have key data2 with right value') - equal(data.params.data3, 'value3', 'query string in url should be passed to server with right value') + equal(data.params.data3, 'value3', 'query string in URL should be passed to server with right value') }) .bindNative('ajax:complete', function() { start() }) .triggerNative('click') diff --git a/actionview/test/ujs/public/test/settings.js b/actionview/test/ujs/public/test/settings.js index 682d044403..ff2b057012 100644 --- a/actionview/test/ujs/public/test/settings.js +++ b/actionview/test/ujs/public/test/settings.js @@ -18,7 +18,7 @@ App.assertPostRequest = function(requestEnv) { } App.assertRequestPath = function(requestEnv, path) { - equal(requestEnv['PATH_INFO'], path, 'request should be sent to right url') + equal(requestEnv['PATH_INFO'], path, 'request should be sent to right URL') } App.getVal = function(el) { |