omg-actionview 8.0.0.alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +25 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +40 -0
- data/app/assets/javascripts/rails-ujs.esm.js +686 -0
- data/app/assets/javascripts/rails-ujs.js +630 -0
- data/lib/action_view/base.rb +316 -0
- data/lib/action_view/buffers.rb +165 -0
- data/lib/action_view/cache_expiry.rb +69 -0
- data/lib/action_view/context.rb +32 -0
- data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
- data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
- data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
- data/lib/action_view/dependency_tracker.rb +41 -0
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +130 -0
- data/lib/action_view/flows.rb +75 -0
- data/lib/action_view/gem_version.rb +17 -0
- data/lib/action_view/helpers/active_model_helper.rb +54 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
- data/lib/action_view/helpers/asset_url_helper.rb +473 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
- data/lib/action_view/helpers/cache_helper.rb +315 -0
- data/lib/action_view/helpers/capture_helper.rb +236 -0
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +42 -0
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +35 -0
- data/lib/action_view/helpers/date_helper.rb +1266 -0
- data/lib/action_view/helpers/debug_helper.rb +38 -0
- data/lib/action_view/helpers/form_helper.rb +2765 -0
- data/lib/action_view/helpers/form_options_helper.rb +927 -0
- data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
- data/lib/action_view/helpers/javascript_helper.rb +96 -0
- data/lib/action_view/helpers/number_helper.rb +165 -0
- data/lib/action_view/helpers/output_safety_helper.rb +70 -0
- data/lib/action_view/helpers/rendering_helper.rb +218 -0
- data/lib/action_view/helpers/sanitize_helper.rb +201 -0
- data/lib/action_view/helpers/tag_helper.rb +621 -0
- data/lib/action_view/helpers/tags/base.rb +138 -0
- data/lib/action_view/helpers/tags/check_box.rb +65 -0
- data/lib/action_view/helpers/tags/checkable.rb +18 -0
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
- data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
- data/lib/action_view/helpers/tags/collection_select.rb +33 -0
- data/lib/action_view/helpers/tags/color_field.rb +26 -0
- data/lib/action_view/helpers/tags/date_field.rb +14 -0
- data/lib/action_view/helpers/tags/date_select.rb +75 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
- data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
- data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
- data/lib/action_view/helpers/tags/email_field.rb +10 -0
- data/lib/action_view/helpers/tags/file_field.rb +26 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
- data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
- data/lib/action_view/helpers/tags/label.rb +84 -0
- data/lib/action_view/helpers/tags/month_field.rb +14 -0
- data/lib/action_view/helpers/tags/number_field.rb +20 -0
- data/lib/action_view/helpers/tags/password_field.rb +14 -0
- data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
- data/lib/action_view/helpers/tags/radio_button.rb +32 -0
- data/lib/action_view/helpers/tags/range_field.rb +10 -0
- data/lib/action_view/helpers/tags/search_field.rb +27 -0
- data/lib/action_view/helpers/tags/select.rb +45 -0
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/tel_field.rb +10 -0
- data/lib/action_view/helpers/tags/text_area.rb +24 -0
- data/lib/action_view/helpers/tags/text_field.rb +33 -0
- data/lib/action_view/helpers/tags/time_field.rb +23 -0
- data/lib/action_view/helpers/tags/time_select.rb +10 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
- data/lib/action_view/helpers/tags/translator.rb +39 -0
- data/lib/action_view/helpers/tags/url_field.rb +10 -0
- data/lib/action_view/helpers/tags/week_field.rb +14 -0
- data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
- data/lib/action_view/helpers/tags.rb +47 -0
- data/lib/action_view/helpers/text_helper.rb +568 -0
- data/lib/action_view/helpers/translation_helper.rb +161 -0
- data/lib/action_view/helpers/url_helper.rb +812 -0
- data/lib/action_view/helpers.rb +68 -0
- data/lib/action_view/layouts.rb +434 -0
- data/lib/action_view/locale/en.yml +56 -0
- data/lib/action_view/log_subscriber.rb +132 -0
- data/lib/action_view/lookup_context.rb +299 -0
- data/lib/action_view/model_naming.rb +14 -0
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +84 -0
- data/lib/action_view/railtie.rb +132 -0
- data/lib/action_view/record_identifier.rb +118 -0
- data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
- data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
- data/lib/action_view/render_parser.rb +40 -0
- data/lib/action_view/renderer/abstract_renderer.rb +186 -0
- data/lib/action_view/renderer/collection_renderer.rb +204 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
- data/lib/action_view/renderer/partial_renderer.rb +267 -0
- data/lib/action_view/renderer/renderer.rb +107 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
- data/lib/action_view/renderer/template_renderer.rb +115 -0
- data/lib/action_view/rendering.rb +190 -0
- data/lib/action_view/routing_url_for.rb +149 -0
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +264 -0
- data/lib/action_view/template/handlers/builder.rb +25 -0
- data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
- data/lib/action_view/template/handlers/erb.rb +157 -0
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +11 -0
- data/lib/action_view/template/handlers.rb +66 -0
- data/lib/action_view/template/html.rb +33 -0
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +30 -0
- data/lib/action_view/template/resolver.rb +212 -0
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +32 -0
- data/lib/action_view/template/types.rb +50 -0
- data/lib/action_view/template.rb +580 -0
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +66 -0
- data/lib/action_view/test_case.rb +449 -0
- data/lib/action_view/testing/resolvers.rb +44 -0
- data/lib/action_view/unbound_template.rb +67 -0
- data/lib/action_view/version.rb +10 -0
- data/lib/action_view/view_paths.rb +117 -0
- data/lib/action_view.rb +104 -0
- metadata +275 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/module/redefine_method"
|
|
4
|
+
require "action_controller"
|
|
5
|
+
require "action_controller/test_case"
|
|
6
|
+
require "action_view"
|
|
7
|
+
|
|
8
|
+
require "rails-dom-testing"
|
|
9
|
+
|
|
10
|
+
module ActionView
|
|
11
|
+
# = Action View Test Case
|
|
12
|
+
#
|
|
13
|
+
# Read more about <tt>ActionView::TestCase</tt> in {Testing Rails Applications}[https://guides.rubyonrails.org/testing.html#testing-view-partials]
|
|
14
|
+
# in the guides.
|
|
15
|
+
class TestCase < ActiveSupport::TestCase
|
|
16
|
+
class TestController < ActionController::Base
|
|
17
|
+
include ActionDispatch::TestProcess
|
|
18
|
+
|
|
19
|
+
attr_accessor :request, :response, :params
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
# Overrides AbstractController::Base#controller_path
|
|
23
|
+
attr_accessor :controller_path
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def controller_path=(path)
|
|
27
|
+
self.class.controller_path = path
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.controller_name
|
|
31
|
+
"test"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def initialize
|
|
35
|
+
super
|
|
36
|
+
self.class.controller_path = ""
|
|
37
|
+
@request = ActionController::TestRequest.create(self.class)
|
|
38
|
+
@response = ActionDispatch::TestResponse.new
|
|
39
|
+
|
|
40
|
+
@request.env.delete("PATH_INFO")
|
|
41
|
+
@params = ActionController::Parameters.new
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module Behavior
|
|
46
|
+
extend ActiveSupport::Concern
|
|
47
|
+
|
|
48
|
+
include ActionDispatch::Assertions, ActionDispatch::TestProcess
|
|
49
|
+
include Rails::Dom::Testing::Assertions
|
|
50
|
+
include ActionController::TemplateAssertions
|
|
51
|
+
include ActionView::Context
|
|
52
|
+
|
|
53
|
+
include ActionDispatch::Routing::PolymorphicRoutes
|
|
54
|
+
|
|
55
|
+
include AbstractController::Helpers
|
|
56
|
+
include ActionView::Helpers
|
|
57
|
+
include ActionView::RecordIdentifier
|
|
58
|
+
include ActionView::RoutingUrlFor
|
|
59
|
+
|
|
60
|
+
include ActiveSupport::Testing::ConstantLookup
|
|
61
|
+
|
|
62
|
+
delegate :lookup_context, to: :controller
|
|
63
|
+
attr_accessor :controller, :request, :output_buffer, :rendered
|
|
64
|
+
|
|
65
|
+
module ClassMethods
|
|
66
|
+
def inherited(descendant) # :nodoc:
|
|
67
|
+
super
|
|
68
|
+
|
|
69
|
+
descendant_content_class = content_class.dup
|
|
70
|
+
|
|
71
|
+
if descendant_content_class.respond_to?(:set_temporary_name)
|
|
72
|
+
descendant_content_class.set_temporary_name("rendered_content")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
descendant.content_class = descendant_content_class
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Register a callable to parse rendered content for a given template
|
|
79
|
+
# format.
|
|
80
|
+
#
|
|
81
|
+
# Each registered parser will also define a +#rendered.[FORMAT]+ helper
|
|
82
|
+
# method, where +[FORMAT]+ corresponds to the value of the
|
|
83
|
+
# +format+ argument.
|
|
84
|
+
#
|
|
85
|
+
# By default, ActionView::TestCase defines parsers for:
|
|
86
|
+
#
|
|
87
|
+
# * +:html+ - returns an instance of +Nokogiri::XML::Node+
|
|
88
|
+
# * +:json+ - returns an instance of ActiveSupport::HashWithIndifferentAccess
|
|
89
|
+
#
|
|
90
|
+
# These pre-registered parsers also define corresponding helpers:
|
|
91
|
+
#
|
|
92
|
+
# * +:html+ - defines +rendered.html+
|
|
93
|
+
# * +:json+ - defines +rendered.json+
|
|
94
|
+
#
|
|
95
|
+
# ==== Parameters
|
|
96
|
+
#
|
|
97
|
+
# [+format+]
|
|
98
|
+
# The name (as a +Symbol+) of the format used to render the content.
|
|
99
|
+
#
|
|
100
|
+
# [+callable+]
|
|
101
|
+
# The parser. A callable object that accepts the rendered string as
|
|
102
|
+
# its sole argument. Alternatively, the parser can be specified as a
|
|
103
|
+
# block.
|
|
104
|
+
#
|
|
105
|
+
# ==== Examples
|
|
106
|
+
#
|
|
107
|
+
# test "renders HTML" do
|
|
108
|
+
# article = Article.create!(title: "Hello, world")
|
|
109
|
+
#
|
|
110
|
+
# render partial: "articles/article", locals: { article: article }
|
|
111
|
+
#
|
|
112
|
+
# assert_pattern { rendered.html.at("main h1") => { content: "Hello, world" } }
|
|
113
|
+
# end
|
|
114
|
+
#
|
|
115
|
+
# test "renders JSON" do
|
|
116
|
+
# article = Article.create!(title: "Hello, world")
|
|
117
|
+
#
|
|
118
|
+
# render formats: :json, partial: "articles/article", locals: { article: article }
|
|
119
|
+
#
|
|
120
|
+
# assert_pattern { rendered.json => { title: "Hello, world" } }
|
|
121
|
+
# end
|
|
122
|
+
#
|
|
123
|
+
# To parse the rendered content into RSS, register a call to +RSS::Parser.parse+:
|
|
124
|
+
#
|
|
125
|
+
# register_parser :rss, -> rendered { RSS::Parser.parse(rendered) }
|
|
126
|
+
#
|
|
127
|
+
# test "renders RSS" do
|
|
128
|
+
# article = Article.create!(title: "Hello, world")
|
|
129
|
+
#
|
|
130
|
+
# render formats: :rss, partial: article
|
|
131
|
+
#
|
|
132
|
+
# assert_equal "Hello, world", rendered.rss.items.last.title
|
|
133
|
+
# end
|
|
134
|
+
#
|
|
135
|
+
# To parse the rendered content into a +Capybara::Simple::Node+,
|
|
136
|
+
# re-register an +:html+ parser with a call to +Capybara.string+:
|
|
137
|
+
#
|
|
138
|
+
# register_parser :html, -> rendered { Capybara.string(rendered) }
|
|
139
|
+
#
|
|
140
|
+
# test "renders HTML" do
|
|
141
|
+
# article = Article.create!(title: "Hello, world")
|
|
142
|
+
#
|
|
143
|
+
# render partial: article
|
|
144
|
+
#
|
|
145
|
+
# rendered.html.assert_css "h1", text: "Hello, world"
|
|
146
|
+
# end
|
|
147
|
+
#
|
|
148
|
+
def register_parser(format, callable = nil, &block)
|
|
149
|
+
parser = callable || block || :itself.to_proc
|
|
150
|
+
content_class.redefine_method(format) do
|
|
151
|
+
parser.call(to_s)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def tests(helper_class)
|
|
156
|
+
case helper_class
|
|
157
|
+
when String, Symbol
|
|
158
|
+
self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize
|
|
159
|
+
when Module
|
|
160
|
+
self.helper_class = helper_class
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def determine_default_helper_class(name)
|
|
165
|
+
determine_constant_from_test_name(name) do |constant|
|
|
166
|
+
Module === constant && !(Class === constant)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def helper_method(*methods)
|
|
171
|
+
# Almost a duplicate from ActionController::Helpers
|
|
172
|
+
methods.flatten.each do |method|
|
|
173
|
+
_helpers_for_modification.module_eval <<~end_eval, __FILE__, __LINE__ + 1
|
|
174
|
+
def #{method}(...) # def current_user(...)
|
|
175
|
+
_test_case.send(:'#{method}', ...) # _test_case.send(:'current_user', ...)
|
|
176
|
+
end # end
|
|
177
|
+
end_eval
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
attr_writer :helper_class
|
|
182
|
+
|
|
183
|
+
def helper_class
|
|
184
|
+
@helper_class ||= determine_default_helper_class(name)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def new(*)
|
|
188
|
+
include_helper_modules!
|
|
189
|
+
super
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
private
|
|
193
|
+
def include_helper_modules!
|
|
194
|
+
helper(helper_class) if helper_class
|
|
195
|
+
include _helpers
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
included do
|
|
200
|
+
class_attribute :content_class, instance_accessor: false, default: RenderedViewContent
|
|
201
|
+
|
|
202
|
+
setup :setup_with_controller
|
|
203
|
+
|
|
204
|
+
register_parser :html, -> rendered { Rails::Dom::Testing.html_document_fragment.parse(rendered) }
|
|
205
|
+
register_parser :json, -> rendered { JSON.parse(rendered, object_class: ActiveSupport::HashWithIndifferentAccess) }
|
|
206
|
+
|
|
207
|
+
ActiveSupport.run_load_hooks(:action_view_test_case, self)
|
|
208
|
+
|
|
209
|
+
helper do
|
|
210
|
+
def protect_against_forgery?
|
|
211
|
+
false
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def _test_case
|
|
215
|
+
controller._test_case
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def setup_with_controller
|
|
221
|
+
controller_class = Class.new(ActionView::TestCase::TestController)
|
|
222
|
+
@controller = controller_class.new
|
|
223
|
+
@request = @controller.request
|
|
224
|
+
@view_flow = ActionView::OutputFlow.new
|
|
225
|
+
@output_buffer = ActionView::OutputBuffer.new
|
|
226
|
+
@rendered = self.class.content_class.new(+"")
|
|
227
|
+
|
|
228
|
+
test_case_instance = self
|
|
229
|
+
controller_class.define_method(:_test_case) { test_case_instance }
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def config
|
|
233
|
+
@controller.config if @controller.respond_to?(:config)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def render(options = {}, local_assigns = {}, &block)
|
|
237
|
+
view.assign(view_assigns)
|
|
238
|
+
@rendered << output = view.render(options, local_assigns, &block)
|
|
239
|
+
output
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def rendered_views
|
|
243
|
+
@_rendered_views ||= RenderedViewsCollection.new
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
##
|
|
247
|
+
# :method: rendered
|
|
248
|
+
#
|
|
249
|
+
# Returns the content rendered by the last +render+ call.
|
|
250
|
+
#
|
|
251
|
+
# The returned object behaves like a string but also exposes a number of methods
|
|
252
|
+
# that allows you to parse the content string in formats registered using
|
|
253
|
+
# <tt>.register_parser</tt>.
|
|
254
|
+
#
|
|
255
|
+
# By default includes the following parsers:
|
|
256
|
+
#
|
|
257
|
+
# +.html+
|
|
258
|
+
#
|
|
259
|
+
# Parse the <tt>rendered</tt> content String into HTML. By default, this means
|
|
260
|
+
# a <tt>Nokogiri::XML::Node</tt>.
|
|
261
|
+
#
|
|
262
|
+
# test "renders HTML" do
|
|
263
|
+
# article = Article.create!(title: "Hello, world")
|
|
264
|
+
#
|
|
265
|
+
# render partial: "articles/article", locals: { article: article }
|
|
266
|
+
#
|
|
267
|
+
# assert_pattern { rendered.html.at("main h1") => { content: "Hello, world" } }
|
|
268
|
+
# end
|
|
269
|
+
#
|
|
270
|
+
# To parse the rendered content into a <tt>Capybara::Simple::Node</tt>,
|
|
271
|
+
# re-register an <tt>:html</tt> parser with a call to
|
|
272
|
+
# <tt>Capybara.string</tt>:
|
|
273
|
+
#
|
|
274
|
+
# register_parser :html, -> rendered { Capybara.string(rendered) }
|
|
275
|
+
#
|
|
276
|
+
# test "renders HTML" do
|
|
277
|
+
# article = Article.create!(title: "Hello, world")
|
|
278
|
+
#
|
|
279
|
+
# render partial: article
|
|
280
|
+
#
|
|
281
|
+
# rendered.html.assert_css "h1", text: "Hello, world"
|
|
282
|
+
# end
|
|
283
|
+
#
|
|
284
|
+
# +.json+
|
|
285
|
+
#
|
|
286
|
+
# Parse the <tt>rendered</tt> content String into JSON. By default, this means
|
|
287
|
+
# a <tt>ActiveSupport::HashWithIndifferentAccess</tt>.
|
|
288
|
+
#
|
|
289
|
+
# test "renders JSON" do
|
|
290
|
+
# article = Article.create!(title: "Hello, world")
|
|
291
|
+
#
|
|
292
|
+
# render formats: :json, partial: "articles/article", locals: { article: article }
|
|
293
|
+
#
|
|
294
|
+
# assert_pattern { rendered.json => { title: "Hello, world" } }
|
|
295
|
+
# end
|
|
296
|
+
|
|
297
|
+
def _routes
|
|
298
|
+
@controller._routes if @controller.respond_to?(:_routes)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
class RenderedViewContent < String # :nodoc:
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Need to experiment if this priority is the best one: rendered => output_buffer
|
|
305
|
+
class RenderedViewsCollection
|
|
306
|
+
def initialize
|
|
307
|
+
@rendered_views ||= Hash.new { |hash, key| hash[key] = [] }
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def add(view, locals)
|
|
311
|
+
@rendered_views[view] ||= []
|
|
312
|
+
@rendered_views[view] << locals
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def locals_for(view)
|
|
316
|
+
@rendered_views[view]
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def rendered_views
|
|
320
|
+
@rendered_views.keys
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def view_rendered?(view, expected_locals)
|
|
324
|
+
locals_for(view).any? do |actual_locals|
|
|
325
|
+
expected_locals.all? { |key, value| value == actual_locals[key] }
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
private
|
|
331
|
+
# Need to experiment if this priority is the best one: rendered => output_buffer
|
|
332
|
+
def document_root_element
|
|
333
|
+
Rails::Dom::Testing.html_document.parse(@rendered.blank? ? @output_buffer.to_str : @rendered).root
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
module Locals
|
|
337
|
+
attr_accessor :rendered_views
|
|
338
|
+
|
|
339
|
+
def render(options = {}, local_assigns = {})
|
|
340
|
+
case options
|
|
341
|
+
when Hash
|
|
342
|
+
if block_given?
|
|
343
|
+
rendered_views.add options[:layout], options[:locals]
|
|
344
|
+
elsif options.key?(:partial)
|
|
345
|
+
rendered_views.add options[:partial], options[:locals]
|
|
346
|
+
end
|
|
347
|
+
else
|
|
348
|
+
rendered_views.add options, local_assigns
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
super
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# The instance of ActionView::Base that is used by +render+.
|
|
356
|
+
def view
|
|
357
|
+
@view ||= begin
|
|
358
|
+
view = @controller.view_context
|
|
359
|
+
view.singleton_class.include(_helpers)
|
|
360
|
+
view.extend(Locals)
|
|
361
|
+
view.rendered_views = rendered_views
|
|
362
|
+
view.output_buffer = output_buffer
|
|
363
|
+
view
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
alias_method :_view, :view
|
|
368
|
+
|
|
369
|
+
INTERNAL_IVARS = [
|
|
370
|
+
:@NAME,
|
|
371
|
+
:@failures,
|
|
372
|
+
:@assertions,
|
|
373
|
+
:@__io__,
|
|
374
|
+
:@_assertion_wrapped,
|
|
375
|
+
:@_assertions,
|
|
376
|
+
:@_result,
|
|
377
|
+
:@_routes,
|
|
378
|
+
:@controller,
|
|
379
|
+
:@_controller,
|
|
380
|
+
:@_request,
|
|
381
|
+
:@_config,
|
|
382
|
+
:@_default_form_builder,
|
|
383
|
+
:@_layouts,
|
|
384
|
+
:@_files,
|
|
385
|
+
:@_rendered_views,
|
|
386
|
+
:@method_name,
|
|
387
|
+
:@output_buffer,
|
|
388
|
+
:@_partials,
|
|
389
|
+
:@passed,
|
|
390
|
+
:@rendered,
|
|
391
|
+
:@request,
|
|
392
|
+
:@routes,
|
|
393
|
+
:@tagged_logger,
|
|
394
|
+
:@_templates,
|
|
395
|
+
:@options,
|
|
396
|
+
:@test_passed,
|
|
397
|
+
:@view,
|
|
398
|
+
:@view_context_class,
|
|
399
|
+
:@view_flow,
|
|
400
|
+
:@_subscribers,
|
|
401
|
+
:@html_document,
|
|
402
|
+
]
|
|
403
|
+
|
|
404
|
+
def _user_defined_ivars
|
|
405
|
+
instance_variables - INTERNAL_IVARS
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Returns a Hash of instance variables and their values, as defined by
|
|
409
|
+
# the user in the test case, which are then assigned to the view being
|
|
410
|
+
# rendered. This is generally intended for internal use and extension
|
|
411
|
+
# frameworks.
|
|
412
|
+
def view_assigns
|
|
413
|
+
Hash[_user_defined_ivars.map do |ivar|
|
|
414
|
+
[ivar[1..-1].to_sym, instance_variable_get(ivar)]
|
|
415
|
+
end]
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def method_missing(selector, ...)
|
|
419
|
+
begin
|
|
420
|
+
routes = @controller.respond_to?(:_routes) && @controller._routes
|
|
421
|
+
rescue
|
|
422
|
+
# Don't call routes, if there is an error on _routes call
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
if routes &&
|
|
426
|
+
(routes.named_routes.route_defined?(selector) ||
|
|
427
|
+
routes.mounted_helpers.method_defined?(selector))
|
|
428
|
+
@controller.__send__(selector, ...)
|
|
429
|
+
else
|
|
430
|
+
super
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def respond_to_missing?(name, include_private = false)
|
|
435
|
+
begin
|
|
436
|
+
routes = @controller.respond_to?(:_routes) && @controller._routes
|
|
437
|
+
rescue
|
|
438
|
+
# Don't call routes, if there is an error on _routes call
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
routes &&
|
|
442
|
+
(routes.named_routes.route_defined?(name) ||
|
|
443
|
+
routes.mounted_helpers.method_defined?(name))
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
include Behavior
|
|
448
|
+
end
|
|
449
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "action_view/template/resolver"
|
|
4
|
+
|
|
5
|
+
module ActionView # :nodoc:
|
|
6
|
+
# Use FixtureResolver in your tests to simulate the presence of files on the
|
|
7
|
+
# file system. This is used internally by Rails' own test suite, and is
|
|
8
|
+
# useful for testing extensions that have no way of knowing what the file
|
|
9
|
+
# system will look like at runtime.
|
|
10
|
+
class FixtureResolver < FileSystemResolver
|
|
11
|
+
def initialize(hash = {})
|
|
12
|
+
super("")
|
|
13
|
+
@hash = hash
|
|
14
|
+
@path = ""
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def data
|
|
18
|
+
@hash
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_s
|
|
22
|
+
@hash.keys.join(", ")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
def template_glob(glob)
|
|
27
|
+
@hash.keys.filter_map do |path|
|
|
28
|
+
"/#{path}" if File.fnmatch(glob, path)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def source_for_template(template)
|
|
33
|
+
@hash[template.from(1)]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class NullResolver < Resolver
|
|
38
|
+
def find_templates(name, prefix, partial, details, locals = [])
|
|
39
|
+
path = TemplatePath.build(name, prefix, partial)
|
|
40
|
+
handler = ActionView::Template::Handlers::Raw
|
|
41
|
+
[ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: nil, variant: nil, locals: locals)]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent/map"
|
|
4
|
+
|
|
5
|
+
module ActionView
|
|
6
|
+
class UnboundTemplate
|
|
7
|
+
attr_reader :virtual_path, :details
|
|
8
|
+
delegate :locale, :format, :variant, :handler, to: :@details
|
|
9
|
+
|
|
10
|
+
def initialize(source, identifier, details:, virtual_path:)
|
|
11
|
+
@source = source
|
|
12
|
+
@identifier = identifier
|
|
13
|
+
@details = details
|
|
14
|
+
@virtual_path = virtual_path
|
|
15
|
+
|
|
16
|
+
@templates = Concurrent::Map.new(initial_capacity: 2)
|
|
17
|
+
@write_lock = Mutex.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def bind_locals(locals)
|
|
21
|
+
unless template = @templates[locals]
|
|
22
|
+
@write_lock.synchronize do
|
|
23
|
+
normalized_locals = normalize_locals(locals)
|
|
24
|
+
|
|
25
|
+
# We need ||=, both to dedup on the normalized locals and to check
|
|
26
|
+
# while holding the lock.
|
|
27
|
+
template = (@templates[normalized_locals] ||= build_template(normalized_locals))
|
|
28
|
+
|
|
29
|
+
if template.strict_locals?
|
|
30
|
+
# Under strict locals, we only need one template.
|
|
31
|
+
# This replaces the @templates Concurrent::Map with a hash which
|
|
32
|
+
# returns this template for every key.
|
|
33
|
+
@templates = Hash.new(template).freeze
|
|
34
|
+
else
|
|
35
|
+
# This may have already been assigned, but we've already de-dup'd so
|
|
36
|
+
# reassignment is fine.
|
|
37
|
+
@templates[locals.dup] = template
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
template
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def built_templates # :nodoc:
|
|
45
|
+
@templates.values
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
def build_template(locals)
|
|
50
|
+
Template.new(
|
|
51
|
+
@source,
|
|
52
|
+
@identifier,
|
|
53
|
+
details.handler_class,
|
|
54
|
+
|
|
55
|
+
format: details.format_or_default,
|
|
56
|
+
variant: variant&.to_s,
|
|
57
|
+
virtual_path: @virtual_path,
|
|
58
|
+
|
|
59
|
+
locals: locals.map(&:to_s)
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def normalize_locals(locals)
|
|
64
|
+
locals.map(&:to_sym).sort!.freeze
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionView
|
|
4
|
+
module ViewPaths
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
ActionView::PathRegistry.set_view_paths(self, ActionView::PathSet.new.freeze)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
delegate :template_exists?, :any_templates?, :view_paths, :formats, :formats=,
|
|
12
|
+
:locale, :locale=, to: :lookup_context
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
def _view_paths
|
|
16
|
+
ActionView::PathRegistry.get_view_paths(self)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def _view_paths=(paths)
|
|
20
|
+
ActionView::PathRegistry.set_view_paths(self, paths)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def _prefixes # :nodoc:
|
|
24
|
+
@_prefixes ||= begin
|
|
25
|
+
return local_prefixes if superclass.abstract?
|
|
26
|
+
|
|
27
|
+
local_prefixes + superclass._prefixes
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def _build_view_paths(paths) # :nodoc:
|
|
32
|
+
return paths if ActionView::PathSet === paths
|
|
33
|
+
|
|
34
|
+
paths = ActionView::PathRegistry.cast_file_system_resolvers(paths)
|
|
35
|
+
ActionView::PathSet.new(paths)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Append a path to the list of view paths for this controller.
|
|
39
|
+
#
|
|
40
|
+
# ==== Parameters
|
|
41
|
+
# * <tt>path</tt> - If a String is provided, it gets converted into
|
|
42
|
+
# the default view path. You may also provide a custom view path
|
|
43
|
+
# (see ActionView::PathSet for more information)
|
|
44
|
+
def append_view_path(path)
|
|
45
|
+
self._view_paths = view_paths + _build_view_paths(path)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Prepend a path to the list of view paths for this controller.
|
|
49
|
+
#
|
|
50
|
+
# ==== Parameters
|
|
51
|
+
# * <tt>path</tt> - If a String is provided, it gets converted into
|
|
52
|
+
# the default view path. You may also provide a custom view path
|
|
53
|
+
# (see ActionView::PathSet for more information)
|
|
54
|
+
def prepend_view_path(path)
|
|
55
|
+
self._view_paths = _build_view_paths(path) + view_paths
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# A list of all of the default view paths for this controller.
|
|
59
|
+
def view_paths
|
|
60
|
+
_view_paths
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Set the view paths.
|
|
64
|
+
#
|
|
65
|
+
# ==== Parameters
|
|
66
|
+
# * <tt>paths</tt> - If a PathSet is provided, use that;
|
|
67
|
+
# otherwise, process the parameter into a PathSet.
|
|
68
|
+
def view_paths=(paths)
|
|
69
|
+
self._view_paths = _build_view_paths(paths)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
# Override this method in your controller if you want to change paths prefixes for finding views.
|
|
74
|
+
# Prefixes defined here will still be added to parents' <tt>._prefixes</tt>.
|
|
75
|
+
def local_prefixes
|
|
76
|
+
[controller_path]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The prefixes used in render "foo" shortcuts.
|
|
81
|
+
def _prefixes # :nodoc:
|
|
82
|
+
self.class._prefixes
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# LookupContext is the object responsible for holding all
|
|
86
|
+
# information required for looking up templates, i.e. view paths and
|
|
87
|
+
# details. Check ActionView::LookupContext for more information.
|
|
88
|
+
def lookup_context
|
|
89
|
+
@_lookup_context ||=
|
|
90
|
+
ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def details_for_lookup
|
|
94
|
+
{}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Append a path to the list of view paths for the current LookupContext.
|
|
98
|
+
#
|
|
99
|
+
# ==== Parameters
|
|
100
|
+
# * <tt>path</tt> - If a String is provided, it gets converted into
|
|
101
|
+
# the default view path. You may also provide a custom view path
|
|
102
|
+
# (see ActionView::PathSet for more information)
|
|
103
|
+
def append_view_path(path)
|
|
104
|
+
lookup_context.append_view_paths(self.class._build_view_paths(path))
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Prepend a path to the list of view paths for the current LookupContext.
|
|
108
|
+
#
|
|
109
|
+
# ==== Parameters
|
|
110
|
+
# * <tt>path</tt> - If a String is provided, it gets converted into
|
|
111
|
+
# the default view path. You may also provide a custom view path
|
|
112
|
+
# (see ActionView::PathSet for more information)
|
|
113
|
+
def prepend_view_path(path)
|
|
114
|
+
lookup_context.prepend_view_paths(self.class._build_view_paths(path))
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|