actionpack 6.1.4 → 7.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +189 -372
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +7 -21
  7. data/lib/abstract_controller/caching/fragments.rb +2 -2
  8. data/lib/abstract_controller/caching.rb +1 -1
  9. data/lib/abstract_controller/callbacks.rb +21 -7
  10. data/lib/abstract_controller/collector.rb +4 -2
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +3 -2
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  15. data/lib/abstract_controller/translation.rb +3 -2
  16. data/lib/abstract_controller/url_for.rb +4 -6
  17. data/lib/action_controller/api.rb +1 -1
  18. data/lib/action_controller/log_subscriber.rb +4 -3
  19. data/lib/action_controller/metal/conditional_get.rb +38 -1
  20. data/lib/action_controller/metal/content_security_policy.rb +1 -1
  21. data/lib/action_controller/metal/cookies.rb +1 -1
  22. data/lib/action_controller/metal/data_streaming.rb +5 -13
  23. data/lib/action_controller/metal/exceptions.rb +19 -30
  24. data/lib/action_controller/metal/flash.rb +6 -2
  25. data/lib/action_controller/metal/helpers.rb +1 -1
  26. data/lib/action_controller/metal/http_authentication.rb +17 -16
  27. data/lib/action_controller/metal/instrumentation.rb +57 -52
  28. data/lib/action_controller/metal/live.rb +42 -2
  29. data/lib/action_controller/metal/mime_responds.rb +3 -3
  30. data/lib/action_controller/metal/params_wrapper.rb +20 -11
  31. data/lib/action_controller/metal/permissions_policy.rb +1 -1
  32. data/lib/action_controller/metal/redirecting.rb +86 -16
  33. data/lib/action_controller/metal/rendering.rb +7 -7
  34. data/lib/action_controller/metal/request_forgery_protection.rb +64 -24
  35. data/lib/action_controller/metal/rescue.rb +1 -1
  36. data/lib/action_controller/metal/streaming.rb +1 -3
  37. data/lib/action_controller/metal/strong_parameters.rb +84 -47
  38. data/lib/action_controller/metal/testing.rb +0 -2
  39. data/lib/action_controller/metal.rb +7 -10
  40. data/lib/action_controller/railtie.rb +49 -6
  41. data/lib/action_controller/test_case.rb +19 -4
  42. data/lib/action_controller.rb +1 -5
  43. data/lib/action_dispatch/http/cache.rb +13 -6
  44. data/lib/action_dispatch/http/content_security_policy.rb +39 -35
  45. data/lib/action_dispatch/http/filter_parameters.rb +5 -0
  46. data/lib/action_dispatch/http/mime_negotiation.rb +13 -3
  47. data/lib/action_dispatch/http/mime_type.rb +9 -11
  48. data/lib/action_dispatch/http/parameters.rb +4 -4
  49. data/lib/action_dispatch/http/permissions_policy.rb +1 -1
  50. data/lib/action_dispatch/http/request.rb +10 -19
  51. data/lib/action_dispatch/http/response.rb +1 -13
  52. data/lib/action_dispatch/http/url.rb +11 -19
  53. data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
  54. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
  55. data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
  56. data/lib/action_dispatch/journey/nodes/node.rb +70 -5
  57. data/lib/action_dispatch/journey/path/pattern.rb +22 -13
  58. data/lib/action_dispatch/journey/route.rb +6 -13
  59. data/lib/action_dispatch/journey/router/utils.rb +2 -2
  60. data/lib/action_dispatch/journey/router.rb +1 -1
  61. data/lib/action_dispatch/journey/routes.rb +3 -3
  62. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  63. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  64. data/lib/action_dispatch/middleware/actionable_exceptions.rb +0 -1
  65. data/lib/action_dispatch/middleware/cookies.rb +8 -4
  66. data/lib/action_dispatch/middleware/debug_exceptions.rb +6 -4
  67. data/lib/action_dispatch/middleware/debug_locks.rb +3 -3
  68. data/lib/action_dispatch/middleware/exception_wrapper.rb +4 -0
  69. data/lib/action_dispatch/middleware/executor.rb +3 -0
  70. data/lib/action_dispatch/middleware/flash.rb +9 -11
  71. data/lib/action_dispatch/middleware/host_authorization.rb +49 -37
  72. data/lib/action_dispatch/middleware/remote_ip.rb +16 -4
  73. data/lib/action_dispatch/middleware/server_timing.rb +33 -0
  74. data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
  75. data/lib/action_dispatch/middleware/show_exceptions.rb +17 -9
  76. data/lib/action_dispatch/middleware/stack.rb +27 -9
  77. data/lib/action_dispatch/middleware/static.rb +2 -6
  78. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +1 -1
  79. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
  80. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
  81. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +4 -3
  82. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +3 -1
  83. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +4 -4
  84. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
  85. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +28 -18
  86. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +3 -3
  87. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +3 -3
  88. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  89. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  90. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +3 -3
  91. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +5 -14
  92. data/lib/action_dispatch/railtie.rb +8 -2
  93. data/lib/action_dispatch/request/session.rb +43 -13
  94. data/lib/action_dispatch/routing/inspector.rb +1 -1
  95. data/lib/action_dispatch/routing/mapper.rb +54 -78
  96. data/lib/action_dispatch/routing/redirection.rb +0 -2
  97. data/lib/action_dispatch/routing/route_set.rb +14 -6
  98. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  99. data/lib/action_dispatch/routing/url_for.rb +1 -2
  100. data/lib/action_dispatch/routing.rb +2 -2
  101. data/lib/action_dispatch/system_test_case.rb +12 -6
  102. data/lib/action_dispatch/system_testing/browser.rb +2 -12
  103. data/lib/action_dispatch/system_testing/driver.rb +35 -11
  104. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +10 -6
  105. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
  106. data/lib/action_dispatch/testing/assertions.rb +2 -5
  107. data/lib/action_dispatch/testing/integration.rb +6 -8
  108. data/lib/action_dispatch/testing/test_process.rb +3 -26
  109. data/lib/action_dispatch.rb +2 -1
  110. data/lib/action_pack/gem_version.rb +4 -4
  111. data/lib/action_pack.rb +1 -1
  112. metadata +19 -17
@@ -4,7 +4,6 @@ require "active_support/core_ext/hash/slice"
4
4
  require "active_support/core_ext/enumerable"
5
5
  require "active_support/core_ext/array/extract_options"
6
6
  require "active_support/core_ext/regexp"
7
- require "active_support/core_ext/symbol/starts_ends_with"
8
7
  require "action_dispatch/routing/redirection"
9
8
  require "action_dispatch/routing/endpoint"
10
9
 
@@ -13,7 +12,7 @@ module ActionDispatch
13
12
  class Mapper
14
13
  URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
15
14
 
16
- class Constraints < Routing::Endpoint #:nodoc:
15
+ class Constraints < Routing::Endpoint # :nodoc:
17
16
  attr_reader :app, :constraints
18
17
 
19
18
  SERVE = ->(app, req) { app.serve req }
@@ -67,11 +66,11 @@ module ActionDispatch
67
66
  end
68
67
  end
69
68
 
70
- class Mapping #:nodoc:
69
+ class Mapping # :nodoc:
71
70
  ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
72
71
  OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
73
72
 
74
- attr_reader :requirements, :defaults, :to, :default_controller,
73
+ attr_reader :path, :requirements, :defaults, :to, :default_controller,
75
74
  :default_action, :required_defaults, :ast, :scope_options
76
75
 
77
76
  def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
@@ -122,31 +121,17 @@ module ActionDispatch
122
121
  @to = intern(to)
123
122
  @default_controller = intern(controller)
124
123
  @default_action = intern(default_action)
125
- @ast = ast
126
124
  @anchor = anchor
127
125
  @via = via
128
126
  @internal = options.delete(:internal)
129
127
  @scope_options = scope_params[:options]
128
+ ast = Journey::Ast.new(ast, formatted)
130
129
 
131
- path_params = []
132
- wildcard_options = {}
133
- ast.each do |node|
134
- if node.symbol?
135
- path_params << node.to_sym
136
- elsif formatted != false && node.star?
137
- # Add a constraint for wildcard route to make it non-greedy and match the
138
- # optional format part of the route by default.
139
- wildcard_options[node.name.to_sym] ||= /.+?/
140
- elsif node.cat?
141
- alter_regex_for_custom_routes(node)
142
- end
143
- end
144
-
145
- options = wildcard_options.merge!(options)
130
+ options = ast.wildcard_options.merge!(options)
146
131
 
147
- options = normalize_options!(options, path_params, scope_params[:module])
132
+ options = normalize_options!(options, ast.path_params, scope_params[:module])
148
133
 
149
- split_options = constraints(options, path_params)
134
+ split_options = constraints(options, ast.path_params)
150
135
 
151
136
  constraints = scope_params[:constraints].merge Hash[split_options[:constraints] || []]
152
137
 
@@ -160,8 +145,8 @@ module ActionDispatch
160
145
  @blocks = blocks(options_constraints)
161
146
  end
162
147
 
163
- requirements, conditions = split_constraints path_params, constraints
164
- verify_regexp_requirements requirements.map(&:last).grep(Regexp)
148
+ requirements, conditions = split_constraints ast.path_params, constraints
149
+ verify_regexp_requirements requirements, ast.wildcard_options
165
150
 
166
151
  formats = normalize_format(formatted)
167
152
 
@@ -169,13 +154,18 @@ module ActionDispatch
169
154
  @conditions = Hash[conditions]
170
155
  @defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
171
156
 
172
- if path_params.include?(:action) && !@requirements.key?(:action)
157
+ if ast.path_params.include?(:action) && !@requirements.key?(:action)
173
158
  @defaults[:action] ||= "index"
174
159
  end
175
160
 
176
161
  @required_defaults = (split_options[:required_defaults] || []).map(&:first)
162
+
163
+ ast.requirements = @requirements
164
+ @path = Journey::Path::Pattern.new(ast, @requirements, JOINED_SEPARATORS, @anchor)
177
165
  end
178
166
 
167
+ JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
168
+
179
169
  def make_route(name, precedence)
180
170
  Journey::Route.new(name: name, app: application, path: path, constraints: conditions,
181
171
  required_defaults: required_defaults, defaults: defaults,
@@ -187,12 +177,6 @@ module ActionDispatch
187
177
  app(@blocks)
188
178
  end
189
179
 
190
- JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
191
-
192
- def path
193
- Journey::Path::Pattern.new(@ast, requirements, JOINED_SEPARATORS, @anchor)
194
- end
195
-
196
180
  def conditions
197
181
  build_conditions @conditions, @set.request_class
198
182
  end
@@ -212,24 +196,6 @@ module ActionDispatch
212
196
  private :request_method
213
197
 
214
198
  private
215
- # Find all the symbol nodes that are adjacent to literal nodes and alter
216
- # the regexp so that Journey will partition them into custom routes.
217
- def alter_regex_for_custom_routes(node)
218
- if node.left.literal? && node.right.symbol?
219
- symbol = node.right
220
- elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
221
- symbol = node.right.left
222
- elsif node.left.symbol? && node.right.literal?
223
- symbol = node.left
224
- elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
225
- symbol = node.left
226
- end
227
-
228
- if symbol
229
- symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
230
- end
231
- end
232
-
233
199
  def intern(object)
234
200
  object.is_a?(String) ? -object : object
235
201
  end
@@ -280,14 +246,18 @@ module ActionDispatch
280
246
  end
281
247
  end
282
248
 
283
- def verify_regexp_requirements(requirements)
284
- requirements.each do |requirement|
285
- if ANCHOR_CHARACTERS_REGEX.match?(requirement.source)
249
+ def verify_regexp_requirements(requirements, wildcard_options)
250
+ requirements.each do |requirement, regex|
251
+ next unless regex.is_a? Regexp
252
+
253
+ if ANCHOR_CHARACTERS_REGEX.match?(regex.source)
286
254
  raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
287
255
  end
288
256
 
289
- if requirement.multiline?
290
- raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
257
+ if regex.multiline?
258
+ next if wildcard_options.key?(requirement)
259
+
260
+ raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{regex.inspect}"
291
261
  end
292
262
  end
293
263
  end
@@ -956,7 +926,7 @@ module ActionDispatch
956
926
  # namespace :admin, as: "sekret" do
957
927
  # resources :posts
958
928
  # end
959
- def namespace(path, options = {})
929
+ def namespace(path, options = {}, &block)
960
930
  path = path.to_s
961
931
 
962
932
  defaults = {
@@ -967,7 +937,7 @@ module ActionDispatch
967
937
  }
968
938
 
969
939
  path_scope(options.delete(:path) { path }) do
970
- scope(defaults.merge!(options)) { yield }
940
+ scope(defaults.merge!(options), &block)
971
941
  end
972
942
  end
973
943
 
@@ -1026,8 +996,8 @@ module ActionDispatch
1026
996
  # constraints(Iphone) do
1027
997
  # resources :iphones
1028
998
  # end
1029
- def constraints(constraints = {})
1030
- scope(constraints: constraints) { yield }
999
+ def constraints(constraints = {}, &block)
1000
+ scope(constraints: constraints, &block)
1031
1001
  end
1032
1002
 
1033
1003
  # Allows you to set default parameters for a route, such as this:
@@ -1156,7 +1126,7 @@ module ActionDispatch
1156
1126
  RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
1157
1127
  CANONICAL_ACTIONS = %w(index create new show update destroy)
1158
1128
 
1159
- class Resource #:nodoc:
1129
+ class Resource # :nodoc:
1160
1130
  attr_reader :controller, :path, :param
1161
1131
 
1162
1132
  def initialize(entities, api_only, shallow, options = {})
@@ -1251,7 +1221,7 @@ module ActionDispatch
1251
1221
  def singleton?; false; end
1252
1222
  end
1253
1223
 
1254
- class SingletonResource < Resource #:nodoc:
1224
+ class SingletonResource < Resource # :nodoc:
1255
1225
  def initialize(entities, api_only, shallow, options)
1256
1226
  super
1257
1227
  @as = nil
@@ -1307,6 +1277,16 @@ module ActionDispatch
1307
1277
  # DELETE /profile
1308
1278
  # POST /profile
1309
1279
  #
1280
+ # If you want instances of a model to work with this resource via
1281
+ # record identification (e.g. in +form_with+ or +redirect_to+), you
1282
+ # will need to call resolve[rdoc-ref:CustomUrls#resolve]:
1283
+ #
1284
+ # resource :profile
1285
+ # resolve('Profile') { [:profile] }
1286
+ #
1287
+ # # Enables this to work with singular routes:
1288
+ # form_with(model: @profile) {}
1289
+ #
1310
1290
  # === Options
1311
1291
  # Takes same options as resources[rdoc-ref:#resources]
1312
1292
  def resource(*resources, &block)
@@ -1517,15 +1497,13 @@ module ActionDispatch
1517
1497
  # with GET, and route to the search action of +PhotosController+. It will also
1518
1498
  # create the <tt>search_photos_url</tt> and <tt>search_photos_path</tt>
1519
1499
  # route helpers.
1520
- def collection
1500
+ def collection(&block)
1521
1501
  unless resource_scope?
1522
1502
  raise ArgumentError, "can't use collection outside resource(s) scope"
1523
1503
  end
1524
1504
 
1525
1505
  with_scope_level(:collection) do
1526
- path_scope(parent_resource.collection_scope) do
1527
- yield
1528
- end
1506
+ path_scope(parent_resource.collection_scope, &block)
1529
1507
  end
1530
1508
  end
1531
1509
 
@@ -1540,7 +1518,7 @@ module ActionDispatch
1540
1518
  # This will recognize <tt>/photos/1/preview</tt> with GET, and route to the
1541
1519
  # preview action of +PhotosController+. It will also create the
1542
1520
  # <tt>preview_photo_url</tt> and <tt>preview_photo_path</tt> helpers.
1543
- def member
1521
+ def member(&block)
1544
1522
  unless resource_scope?
1545
1523
  raise ArgumentError, "can't use member outside resource(s) scope"
1546
1524
  end
@@ -1548,27 +1526,25 @@ module ActionDispatch
1548
1526
  with_scope_level(:member) do
1549
1527
  if shallow?
1550
1528
  shallow_scope {
1551
- path_scope(parent_resource.member_scope) { yield }
1529
+ path_scope(parent_resource.member_scope, &block)
1552
1530
  }
1553
1531
  else
1554
- path_scope(parent_resource.member_scope) { yield }
1532
+ path_scope(parent_resource.member_scope, &block)
1555
1533
  end
1556
1534
  end
1557
1535
  end
1558
1536
 
1559
- def new
1537
+ def new(&block)
1560
1538
  unless resource_scope?
1561
1539
  raise ArgumentError, "can't use new outside resource(s) scope"
1562
1540
  end
1563
1541
 
1564
1542
  with_scope_level(:new) do
1565
- path_scope(parent_resource.new_scope(action_path(:new))) do
1566
- yield
1567
- end
1543
+ path_scope(parent_resource.new_scope(action_path(:new)), &block)
1568
1544
  end
1569
1545
  end
1570
1546
 
1571
- def nested
1547
+ def nested(&block)
1572
1548
  unless resource_scope?
1573
1549
  raise ArgumentError, "can't use nested outside resource(s) scope"
1574
1550
  end
@@ -1577,12 +1553,12 @@ module ActionDispatch
1577
1553
  if shallow? && shallow_nesting_depth >= 1
1578
1554
  shallow_scope do
1579
1555
  path_scope(parent_resource.nested_scope) do
1580
- scope(nested_options) { yield }
1556
+ scope(nested_options, &block)
1581
1557
  end
1582
1558
  end
1583
1559
  else
1584
1560
  path_scope(parent_resource.nested_scope) do
1585
- scope(nested_options) { yield }
1561
+ scope(nested_options, &block)
1586
1562
  end
1587
1563
  end
1588
1564
  end
@@ -1768,10 +1744,10 @@ module ActionDispatch
1768
1744
  @scope = @scope.parent
1769
1745
  end
1770
1746
 
1771
- def resource_scope(resource)
1747
+ def resource_scope(resource, &block)
1772
1748
  @scope = @scope.new(scope_level_resource: resource)
1773
1749
 
1774
- controller(resource.resource_scope) { yield }
1750
+ controller(resource.resource_scope, &block)
1775
1751
  ensure
1776
1752
  @scope = @scope.parent
1777
1753
  end
@@ -1889,7 +1865,7 @@ module ActionDispatch
1889
1865
  end
1890
1866
 
1891
1867
  def map_match(paths, options)
1892
- if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
1868
+ if (on = options[:on]) && !VALID_ON_OPTIONS.include?(on)
1893
1869
  raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
1894
1870
  end
1895
1871
 
@@ -2300,7 +2276,7 @@ module ActionDispatch
2300
2276
  NULL = Scope.new(nil, nil)
2301
2277
  end
2302
2278
 
2303
- def initialize(set) #:nodoc:
2279
+ def initialize(set) # :nodoc:
2304
2280
  @set = set
2305
2281
  @draw_paths = set.draw_paths
2306
2282
  @scope = Scope.new(path_names: @set.resources_path_names)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "action_dispatch/http/request"
4
- require "active_support/core_ext/uri"
5
3
  require "active_support/core_ext/array/extract_options"
6
4
  require "rack/utils"
7
5
  require "action_controller/metal/exceptions"
@@ -6,7 +6,6 @@ require "active_support/core_ext/module/redefine_method"
6
6
  require "active_support/core_ext/module/remove_method"
7
7
  require "active_support/core_ext/array/extract_options"
8
8
  require "action_controller/metal/exceptions"
9
- require "action_dispatch/http/request"
10
9
  require "action_dispatch/routing/endpoint"
11
10
 
12
11
  module ActionDispatch
@@ -132,8 +131,8 @@ module ActionDispatch
132
131
  alias [] get
133
132
  alias clear clear!
134
133
 
135
- def each
136
- routes.each { |name, route| yield name, route }
134
+ def each(&block)
135
+ routes.each(&block)
137
136
  self
138
137
  end
139
138
 
@@ -597,14 +596,14 @@ module ActionDispatch
597
596
  if route.segment_keys.include?(:controller)
598
597
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
599
598
  Using a dynamic :controller segment in a route is deprecated and
600
- will be removed in Rails 6.2.
599
+ will be removed in Rails 7.0.
601
600
  MSG
602
601
  end
603
602
 
604
603
  if route.segment_keys.include?(:action)
605
604
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
606
605
  Using a dynamic :action segment in a route is deprecated and
607
- will be removed in Rails 6.2.
606
+ will be removed in Rails 7.0.
608
607
  MSG
609
608
  end
610
609
 
@@ -821,10 +820,19 @@ module ActionDispatch
821
820
 
822
821
  route_with_params = generate(route_name, path_options, recall)
823
822
  path = route_with_params.path(method_name)
823
+
824
+ if options[:trailing_slash] && !options[:format] && !path.end_with?("/")
825
+ path += "/"
826
+ end
827
+
824
828
  params = route_with_params.params
825
829
 
826
830
  if options.key? :params
827
- params.merge! options[:params]
831
+ if options[:params]&.respond_to?(:to_hash)
832
+ params.merge! options[:params]
833
+ else
834
+ params[:params] = options[:params]
835
+ end
828
836
  end
829
837
 
830
838
  options[:path] = path
@@ -4,7 +4,7 @@ require "active_support/core_ext/array/extract_options"
4
4
 
5
5
  module ActionDispatch
6
6
  module Routing
7
- class RoutesProxy #:nodoc:
7
+ class RoutesProxy # :nodoc:
8
8
  include ActionDispatch::Routing::UrlFor
9
9
 
10
10
  attr_accessor :scope, :routes
@@ -103,11 +103,10 @@ module ActionDispatch
103
103
  include(*_url_for_modules) if respond_to?(:_url_for_modules)
104
104
  end
105
105
 
106
- def initialize(*)
106
+ def initialize(...)
107
107
  @_routes = nil
108
108
  super
109
109
  end
110
- ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true)
111
110
 
112
111
  # Hook overridden in controller to add request information
113
112
  # with +default_url_options+. Application logic should not
@@ -255,7 +255,7 @@ module ActionDispatch
255
255
  autoload :UrlFor
256
256
  autoload :PolymorphicRoutes
257
257
 
258
- SEPARATORS = %w( / . ? ) #:nodoc:
259
- HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
258
+ SEPARATORS = %w( / . ? ) # :nodoc:
259
+ HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] # :nodoc:
260
260
  end
261
261
  end
@@ -72,8 +72,8 @@ module ActionDispatch
72
72
  # Headless browsers such as headless Chrome and headless Firefox are also supported.
73
73
  # You can use these browsers by setting the +:using+ argument to +:headless_chrome+ or +:headless_firefox+.
74
74
  #
75
- # To use a headless driver, like Poltergeist, update your Gemfile to use
76
- # Poltergeist instead of Selenium and then declare the driver name in the
75
+ # To use a headless driver, like Cuprite, update your Gemfile to use
76
+ # Cuprite instead of Selenium and then declare the driver name in the
77
77
  # +application_system_test_case.rb+ file. In this case, you would leave out
78
78
  # the +:using+ option because the driver is headless, but you can still use
79
79
  # +:screen_size+ to change the size of the browser screen, also you can use
@@ -81,10 +81,10 @@ module ActionDispatch
81
81
  # driver documentation to learn about supported options.
82
82
  #
83
83
  # require "test_helper"
84
- # require "capybara/poltergeist"
84
+ # require "capybara/cuprite"
85
85
  #
86
86
  # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
87
- # driven_by :poltergeist, screen_size: [1400, 1400], options:
87
+ # driven_by :cuprite, screen_size: [1400, 1400], options:
88
88
  # { js_errors: true }
89
89
  # end
90
90
  #
@@ -115,6 +115,8 @@ module ActionDispatch
115
115
  include SystemTesting::TestHelpers::SetupAndTeardown
116
116
  include SystemTesting::TestHelpers::ScreenshotHelper
117
117
 
118
+ DEFAULT_HOST = "http://127.0.0.1"
119
+
118
120
  def initialize(*) # :nodoc:
119
121
  super
120
122
  self.class.driven_by(:selenium) unless self.class.driver?
@@ -140,7 +142,7 @@ module ActionDispatch
140
142
  #
141
143
  # Examples:
142
144
  #
143
- # driven_by :poltergeist
145
+ # driven_by :cuprite
144
146
  #
145
147
  # driven_by :selenium, screen_size: [800, 800]
146
148
  #
@@ -166,7 +168,11 @@ module ActionDispatch
166
168
  include ActionDispatch.test_app.routes.mounted_helpers
167
169
 
168
170
  def url_options
169
- default_url_options.reverse_merge(host: Capybara.app_host || Capybara.current_session.server_url)
171
+ default_url_options.reverse_merge(host: app_host)
172
+ end
173
+
174
+ def app_host
175
+ Capybara.app_host || Capybara.current_session.server_url || DEFAULT_HOST
170
176
  end
171
177
  end.new
172
178
  end
@@ -33,19 +33,9 @@ module ActionDispatch
33
33
  def preload
34
34
  case type
35
35
  when :chrome
36
- if ::Selenium::WebDriver::Service.respond_to? :driver_path=
37
- ::Selenium::WebDriver::Chrome::Service.driver_path&.call
38
- else
39
- # Selenium <= v3.141.0
40
- ::Selenium::WebDriver::Chrome.driver_path
41
- end
36
+ ::Selenium::WebDriver::Chrome::Service.driver_path&.call
42
37
  when :firefox
43
- if ::Selenium::WebDriver::Service.respond_to? :driver_path=
44
- ::Selenium::WebDriver::Firefox::Service.driver_path&.call
45
- else
46
- # Selenium <= v3.141.0
47
- ::Selenium::WebDriver::Firefox.driver_path
48
- end
38
+ ::Selenium::WebDriver::Firefox::Service.driver_path&.call
49
39
  end
50
40
  end
51
41
 
@@ -3,16 +3,30 @@
3
3
  module ActionDispatch
4
4
  module SystemTesting
5
5
  class Driver # :nodoc:
6
- def initialize(name, **options, &capabilities)
7
- @name = name
8
- @browser = Browser.new(options[:using])
6
+ attr_reader :name
7
+
8
+ def initialize(driver_type, **options, &capabilities)
9
+ @driver_type = driver_type
9
10
  @screen_size = options[:screen_size]
10
11
  @options = options[:options] || {}
12
+ @name = @options.delete(:name) || driver_type
11
13
  @capabilities = capabilities
12
14
 
13
- if name == :selenium
15
+ if [:poltergeist, :webkit].include?(driver_type)
16
+ ActiveSupport::Deprecation.warn <<~MSG.squish
17
+ Poltergeist and capybara-webkit are not maintained already.
18
+ Driver registration of :poltergeist or :webkit is deprecated and will be removed in Rails 7.1.
19
+ You can still use :selenium, and also :cuprite is available for alternative to Poltergeist.
20
+ MSG
21
+ end
22
+
23
+ if driver_type == :selenium
24
+ gem "selenium-webdriver", ">= 4.0.0"
14
25
  require "selenium/webdriver"
26
+ @browser = Browser.new(options[:using])
15
27
  @browser.preload
28
+ else
29
+ @browser = nil
16
30
  end
17
31
  end
18
32
 
@@ -24,27 +38,29 @@ module ActionDispatch
24
38
 
25
39
  private
26
40
  def registerable?
27
- [:selenium, :poltergeist, :webkit].include?(@name)
41
+ [:selenium, :poltergeist, :webkit, :cuprite, :rack_test].include?(@driver_type)
28
42
  end
29
43
 
30
44
  def register
31
- @browser.configure(&@capabilities)
45
+ @browser&.configure(&@capabilities)
32
46
 
33
- Capybara.register_driver @name do |app|
34
- case @name
47
+ Capybara.register_driver name do |app|
48
+ case @driver_type
35
49
  when :selenium then register_selenium(app)
36
50
  when :poltergeist then register_poltergeist(app)
37
51
  when :webkit then register_webkit(app)
52
+ when :cuprite then register_cuprite(app)
53
+ when :rack_test then register_rack_test(app)
38
54
  end
39
55
  end
40
56
  end
41
57
 
42
58
  def browser_options
43
- @options.merge(options: @browser.options).compact
59
+ @options.merge(capabilities: @browser.options).compact
44
60
  end
45
61
 
46
62
  def register_selenium(app)
47
- Capybara::Selenium::Driver.new(app, **{ browser: @browser.type }.merge(browser_options)).tap do |driver|
63
+ Capybara::Selenium::Driver.new(app, browser: @browser.type, **browser_options).tap do |driver|
48
64
  driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size)
49
65
  end
50
66
  end
@@ -59,8 +75,16 @@ module ActionDispatch
59
75
  end
60
76
  end
61
77
 
78
+ def register_cuprite(app)
79
+ Capybara::Cuprite::Driver.new(app, @options.merge(window_size: @screen_size))
80
+ end
81
+
82
+ def register_rack_test(app)
83
+ Capybara::RackTest::Driver.new(app, respect_data_method: true, **@options)
84
+ end
85
+
62
86
  def setup
63
- Capybara.current_driver = @name
87
+ Capybara.current_driver = name
64
88
  end
65
89
  end
66
90
  end
@@ -15,8 +15,11 @@ module ActionDispatch
15
15
  #
16
16
  # The screenshot will be displayed in your console, if supported.
17
17
  #
18
+ # The default screenshots directory is +tmp/screenshots+ but you can set a different
19
+ # one with +Capybara.save_path+
20
+ #
18
21
  # You can set the +RAILS_SYSTEM_TESTING_SCREENSHOT_HTML+ environment variable to
19
- # save the HTML from the page that is being screenhoted so you can investigate the
22
+ # save the HTML from the page that is being screenshotted so you can investigate the
20
23
  # elements on the page at the time of the screenshot
21
24
  #
22
25
  # You can set the +RAILS_SYSTEM_TESTING_SCREENSHOT+ environment variable to
@@ -37,10 +40,7 @@ module ActionDispatch
37
40
  # Takes a screenshot of the current page in the browser if the test
38
41
  # failed.
39
42
  #
40
- # +take_failed_screenshot+ is included in <tt>application_system_test_case.rb</tt>
41
- # that is generated with the application. To take screenshots when a test
42
- # fails add +take_failed_screenshot+ to the teardown block before clearing
43
- # sessions.
43
+ # +take_failed_screenshot+ is called during system test teardown.
44
44
  def take_failed_screenshot
45
45
  take_screenshot if failed? && supports_screenshot?
46
46
  end
@@ -76,7 +76,11 @@ module ActionDispatch
76
76
  end
77
77
 
78
78
  def absolute_path
79
- Rails.root.join("tmp/screenshots/#{image_name}")
79
+ Rails.root.join(screenshots_dir, image_name)
80
+ end
81
+
82
+ def screenshots_dir
83
+ Capybara.save_path.presence || "tmp/screenshots"
80
84
  end
81
85
 
82
86
  def absolute_image_path
@@ -4,14 +4,6 @@ module ActionDispatch
4
4
  module SystemTesting
5
5
  module TestHelpers
6
6
  module SetupAndTeardown # :nodoc:
7
- def host!(host)
8
- ActiveSupport::Deprecation.warn \
9
- "ActionDispatch::SystemTestCase#host! is deprecated with no replacement. " \
10
- "Set Capybara.app_host directly or rely on Capybara's default host."
11
-
12
- Capybara.app_host = host
13
- end
14
-
15
7
  def before_teardown
16
8
  take_failed_screenshot
17
9
  ensure
@@ -1,14 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails-dom-testing"
4
+ require "action_dispatch/testing/assertions/response"
5
+ require "action_dispatch/testing/assertions/routing"
4
6
 
5
7
  module ActionDispatch
6
8
  module Assertions
7
- autoload :ResponseAssertions, "action_dispatch/testing/assertions/response"
8
- autoload :RoutingAssertions, "action_dispatch/testing/assertions/routing"
9
-
10
- extend ActiveSupport::Concern
11
-
12
9
  include ResponseAssertions
13
10
  include RoutingAssertions
14
11
  include Rails::Dom::Testing::Assertions