actionpack 7.0.8.1 → 7.1.0.beta1

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +320 -393
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/abstract_controller/base.rb +19 -10
  6. data/lib/abstract_controller/caching/fragments.rb +2 -0
  7. data/lib/abstract_controller/callbacks.rb +31 -6
  8. data/lib/abstract_controller/deprecator.rb +7 -0
  9. data/lib/abstract_controller/helpers.rb +61 -18
  10. data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
  11. data/lib/abstract_controller/rendering.rb +3 -3
  12. data/lib/abstract_controller/translation.rb +1 -27
  13. data/lib/abstract_controller/url_for.rb +2 -0
  14. data/lib/abstract_controller.rb +6 -0
  15. data/lib/action_controller/api.rb +5 -3
  16. data/lib/action_controller/base.rb +3 -17
  17. data/lib/action_controller/caching.rb +2 -0
  18. data/lib/action_controller/deprecator.rb +7 -0
  19. data/lib/action_controller/form_builder.rb +2 -0
  20. data/lib/action_controller/log_subscriber.rb +16 -4
  21. data/lib/action_controller/metal/content_security_policy.rb +1 -1
  22. data/lib/action_controller/metal/data_streaming.rb +2 -0
  23. data/lib/action_controller/metal/default_headers.rb +2 -0
  24. data/lib/action_controller/metal/etag_with_flash.rb +2 -0
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  26. data/lib/action_controller/metal/exceptions.rb +8 -0
  27. data/lib/action_controller/metal/head.rb +8 -6
  28. data/lib/action_controller/metal/helpers.rb +3 -14
  29. data/lib/action_controller/metal/http_authentication.rb +11 -4
  30. data/lib/action_controller/metal/implicit_render.rb +5 -3
  31. data/lib/action_controller/metal/instrumentation.rb +8 -1
  32. data/lib/action_controller/metal/live.rb +24 -0
  33. data/lib/action_controller/metal/mime_responds.rb +2 -2
  34. data/lib/action_controller/metal/params_wrapper.rb +3 -1
  35. data/lib/action_controller/metal/permissions_policy.rb +1 -1
  36. data/lib/action_controller/metal/redirecting.rb +6 -6
  37. data/lib/action_controller/metal/renderers.rb +2 -2
  38. data/lib/action_controller/metal/rendering.rb +0 -7
  39. data/lib/action_controller/metal/request_forgery_protection.rb +138 -50
  40. data/lib/action_controller/metal/rescue.rb +2 -0
  41. data/lib/action_controller/metal/streaming.rb +70 -30
  42. data/lib/action_controller/metal/strong_parameters.rb +89 -50
  43. data/lib/action_controller/metal/url_for.rb +7 -0
  44. data/lib/action_controller/metal.rb +79 -21
  45. data/lib/action_controller/railtie.rb +22 -9
  46. data/lib/action_controller/renderer.rb +98 -65
  47. data/lib/action_controller/test_case.rb +15 -5
  48. data/lib/action_controller.rb +8 -1
  49. data/lib/action_dispatch/constants.rb +32 -0
  50. data/lib/action_dispatch/deprecator.rb +7 -0
  51. data/lib/action_dispatch/http/cache.rb +1 -3
  52. data/lib/action_dispatch/http/content_security_policy.rb +9 -8
  53. data/lib/action_dispatch/http/filter_parameters.rb +11 -5
  54. data/lib/action_dispatch/http/headers.rb +2 -0
  55. data/lib/action_dispatch/http/mime_negotiation.rb +21 -21
  56. data/lib/action_dispatch/http/mime_type.rb +35 -12
  57. data/lib/action_dispatch/http/mime_types.rb +3 -1
  58. data/lib/action_dispatch/http/parameters.rb +1 -1
  59. data/lib/action_dispatch/http/permissions_policy.rb +39 -17
  60. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  61. data/lib/action_dispatch/http/request.rb +48 -14
  62. data/lib/action_dispatch/http/response.rb +78 -59
  63. data/lib/action_dispatch/http/upload.rb +2 -0
  64. data/lib/action_dispatch/journey/formatter.rb +8 -2
  65. data/lib/action_dispatch/journey/path/pattern.rb +14 -14
  66. data/lib/action_dispatch/journey/route.rb +3 -2
  67. data/lib/action_dispatch/journey/router.rb +5 -4
  68. data/lib/action_dispatch/journey/routes.rb +2 -2
  69. data/lib/action_dispatch/log_subscriber.rb +23 -0
  70. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
  71. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  72. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  73. data/lib/action_dispatch/middleware/cookies.rb +81 -98
  74. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
  75. data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
  76. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  77. data/lib/action_dispatch/middleware/exception_wrapper.rb +181 -27
  78. data/lib/action_dispatch/middleware/executor.rb +1 -1
  79. data/lib/action_dispatch/middleware/flash.rb +7 -0
  80. data/lib/action_dispatch/middleware/host_authorization.rb +6 -3
  81. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  82. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  83. data/lib/action_dispatch/middleware/remote_ip.rb +17 -16
  84. data/lib/action_dispatch/middleware/request_id.rb +2 -0
  85. data/lib/action_dispatch/middleware/server_timing.rb +4 -4
  86. data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
  87. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  88. data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
  89. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  90. data/lib/action_dispatch/middleware/show_exceptions.rb +19 -15
  91. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  92. data/lib/action_dispatch/middleware/stack.rb +7 -2
  93. data/lib/action_dispatch/middleware/static.rb +12 -8
  94. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  95. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  96. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  97. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  98. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  99. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  100. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  101. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  102. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  103. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  104. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  105. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  107. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +46 -37
  108. data/lib/action_dispatch/railtie.rb +14 -4
  109. data/lib/action_dispatch/request/session.rb +16 -6
  110. data/lib/action_dispatch/request/utils.rb +8 -3
  111. data/lib/action_dispatch/routing/inspector.rb +54 -6
  112. data/lib/action_dispatch/routing/mapper.rb +26 -14
  113. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  114. data/lib/action_dispatch/routing/redirection.rb +15 -6
  115. data/lib/action_dispatch/routing/route_set.rb +52 -22
  116. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  117. data/lib/action_dispatch/routing/url_for.rb +5 -1
  118. data/lib/action_dispatch/routing.rb +4 -4
  119. data/lib/action_dispatch/system_test_case.rb +3 -3
  120. data/lib/action_dispatch/system_testing/browser.rb +5 -6
  121. data/lib/action_dispatch/system_testing/driver.rb +13 -21
  122. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
  123. data/lib/action_dispatch/testing/assertions/response.rb +13 -6
  124. data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
  125. data/lib/action_dispatch/testing/assertions.rb +3 -1
  126. data/lib/action_dispatch/testing/integration.rb +27 -17
  127. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  128. data/lib/action_dispatch/testing/test_process.rb +4 -3
  129. data/lib/action_dispatch/testing/test_request.rb +1 -1
  130. data/lib/action_dispatch/testing/test_response.rb +23 -9
  131. data/lib/action_dispatch.rb +37 -4
  132. data/lib/action_pack/gem_version.rb +4 -4
  133. data/lib/action_pack/version.rb +1 -1
  134. data/lib/action_pack.rb +1 -1
  135. metadata +52 -30
@@ -34,20 +34,20 @@ module ActionDispatch
34
34
  if @raise_on_name_error
35
35
  raise
36
36
  else
37
- [404, { "X-Cascade" => "pass" }, []]
37
+ [404, { Constants::X_CASCADE => "pass" }, []]
38
38
  end
39
39
  end
40
40
 
41
- private
42
- def controller(req)
43
- req.controller_class
44
- rescue NameError => e
45
- raise ActionController::RoutingError, e.message, e.backtrace
46
- end
41
+ private
42
+ def controller(req)
43
+ req.controller_class
44
+ rescue NameError => e
45
+ raise ActionController::RoutingError, e.message, e.backtrace
46
+ end
47
47
 
48
- def dispatch(controller, action, req, res)
49
- controller.dispatch(action, req, res)
50
- end
48
+ def dispatch(controller, action, req, res)
49
+ controller.dispatch(action, req, res)
50
+ end
51
51
  end
52
52
 
53
53
  class StaticDispatcher < Dispatcher
@@ -282,7 +282,7 @@ module ActionDispatch
282
282
 
283
283
  if args.size < path_params_size
284
284
  path_params -= controller_options.keys
285
- path_params -= result.keys
285
+ path_params -= (result[:path_params] || {}).merge(result).keys
286
286
  else
287
287
  path_params = path_params.dup
288
288
  end
@@ -375,6 +375,7 @@ module ActionDispatch
375
375
  @disable_clear_and_finalize = false
376
376
  @finalized = false
377
377
  @env_key = "ROUTES_#{object_id}_SCRIPT_NAME"
378
+ @default_env = nil
378
379
 
379
380
  @set = Journey::Routes.new
380
381
  @router = Journey::Router.new @set
@@ -405,6 +406,25 @@ module ActionDispatch
405
406
  end
406
407
  private :make_request
407
408
 
409
+ def default_env
410
+ if default_url_options != @default_env&.[]("action_dispatch.routes.default_url_options")
411
+ url_options = default_url_options.dup.freeze
412
+ uri = URI(ActionDispatch::Http::URL.full_url_for(host: "example.org", **url_options))
413
+
414
+ @default_env = {
415
+ "action_dispatch.routes" => self,
416
+ "action_dispatch.routes.default_url_options" => url_options,
417
+ "HTTPS" => uri.scheme == "https" ? "on" : "off",
418
+ "rack.url_scheme" => uri.scheme,
419
+ "HTTP_HOST" => uri.port == uri.default_port ? uri.host : "#{uri.host}:#{uri.port}",
420
+ "SCRIPT_NAME" => uri.path.chomp("/"),
421
+ "rack.input" => "",
422
+ }.freeze
423
+ end
424
+
425
+ @default_env
426
+ end
427
+
408
428
  def draw(&block)
409
429
  clear! unless @disable_clear_and_finalize
410
430
  eval_block(block)
@@ -574,6 +594,20 @@ module ActionDispatch
574
594
  end
575
595
 
576
596
  private :_generate_paths_by_default
597
+
598
+ # If the module is included more than once (for example, in a subclass
599
+ # of an ancestor that includes the module), ensure that the `_routes`
600
+ # singleton and instance methods return the desired route set by
601
+ # including a new copy of the module (recursively if necessary). Note
602
+ # that this method is called for each inclusion, whereas the above
603
+ # `included` block is run only for the initial inclusion of each copy.
604
+ def self.included(base)
605
+ super
606
+ if !base._routes.equal?(@_proxy._routes)
607
+ @dup_for_reinclude ||= self.dup
608
+ base.include @dup_for_reinclude
609
+ end
610
+ end
577
611
  end
578
612
  end
579
613
 
@@ -596,16 +630,16 @@ module ActionDispatch
596
630
  named_routes[name] = route if name
597
631
 
598
632
  if route.segment_keys.include?(:controller)
599
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
633
+ ActionDispatch.deprecator.warn(<<-MSG.squish)
600
634
  Using a dynamic :controller segment in a route is deprecated and
601
- will be removed in Rails 7.1.
635
+ will be removed in Rails 7.2.
602
636
  MSG
603
637
  end
604
638
 
605
639
  if route.segment_keys.include?(:action)
606
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
640
+ ActionDispatch.deprecator.warn(<<-MSG.squish)
607
641
  Using a dynamic :action segment in a route is deprecated and
608
- will be removed in Rails 7.1.
642
+ will be removed in Rails 7.2.
609
643
  MSG
610
644
  end
611
645
 
@@ -779,18 +813,14 @@ module ActionDispatch
779
813
 
780
814
  RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
781
815
  :trailing_slash, :anchor, :params, :only_path, :script_name,
782
- :original_script_name, :relative_url_root]
816
+ :original_script_name]
783
817
 
784
818
  def optimize_routes_generation?
785
819
  default_url_options.empty?
786
820
  end
787
821
 
788
822
  def find_script_name(options)
789
- options.delete(:script_name) || find_relative_url_root(options) || ""
790
- end
791
-
792
- def find_relative_url_root(options)
793
- options.delete(:relative_url_root) || relative_url_root
823
+ options.delete(:script_name) || relative_url_root || ""
794
824
  end
795
825
 
796
826
  def path_for(options, route_name = nil, reserved = RESERVED_OPTIONS)
@@ -854,7 +884,7 @@ module ActionDispatch
854
884
 
855
885
  def recognize_path(path, environment = {})
856
886
  method = (environment[:method] || "GET").to_s.upcase
857
- path = Journey::Router::Utils.normalize_path(path) unless %r{://}.match?(path)
887
+ path = Journey::Router::Utils.normalize_path(path) unless path&.include?("://")
858
888
  extras = environment[:extras] || {}
859
889
 
860
890
  begin
@@ -29,7 +29,7 @@ module ActionDispatch
29
29
 
30
30
  def method_missing(method, *args)
31
31
  if @helpers.respond_to?(method)
32
- self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
32
+ instance_eval <<-RUBY, __FILE__, __LINE__ + 1
33
33
  def #{method}(*args)
34
34
  options = args.extract_options!
35
35
  options = url_options.merge((options || {}).symbolize_keys)
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ActionDispatch
4
4
  module Routing
5
+ # = Action Dispatch Routing \UrlFor
6
+ #
5
7
  # In <tt>config/routes.rb</tt> you define URL-to-controller mappings, but the reverse
6
8
  # is also possible: a URL can be generated from one of your routing definitions.
7
9
  # URL generation functionality is centralized in this module.
@@ -37,7 +39,7 @@ module ActionDispatch
37
39
  # # => "/users/new?message=Welcome%21"
38
40
  #
39
41
  # Notice the <tt>only_path: true</tt> part. This is because UrlFor has no
40
- # information about the website hostname that your Rails app is serving. So if you
42
+ # information about the website hostname that your \Rails app is serving. So if you
41
43
  # want to include the hostname as well, then you must also pass the <tt>:host</tt>
42
44
  # argument:
43
45
  #
@@ -134,6 +136,8 @@ module ActionDispatch
134
136
  # * <tt>:port</tt> - Optionally specify the port to connect to.
135
137
  # * <tt>:anchor</tt> - An anchor name to be appended to the path.
136
138
  # * <tt>:params</tt> - The query parameters to be appended to the path.
139
+ # * <tt>:path_params</tt> - The query parameters that will only be used
140
+ # for the named dynamic segments of path. If unused, they will be discarded.
137
141
  # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in <tt>"/archive/2009/"</tt>.
138
142
  # * <tt>:script_name</tt> - Specifies application path relative to domain root. If provided, prepends application path.
139
143
  #
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/string/filters"
4
-
5
3
  module ActionDispatch
6
4
  # The routing module provides URL rewriting in native Ruby. It's a way to
7
5
  # redirect incoming requests to controllers and actions. This replaces
@@ -240,7 +238,7 @@ module ActionDispatch
240
238
  #
241
239
  # == View a list of all your routes
242
240
  #
243
- # rails routes
241
+ # bin/rails routes
244
242
  #
245
243
  # Target a specific controller with <tt>-c</tt>, or grep routes
246
244
  # using <tt>-g</tt>. Useful in conjunction with <tt>--expanded</tt>
@@ -250,7 +248,9 @@ module ActionDispatch
250
248
 
251
249
  autoload :Mapper
252
250
  autoload :RouteSet
253
- autoload :RoutesProxy
251
+ eager_autoload do
252
+ autoload :RoutesProxy
253
+ end
254
254
  autoload :UrlFor
255
255
  autoload :PolymorphicRoutes
256
256
 
@@ -51,7 +51,7 @@ module ActionDispatch
51
51
  # driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
52
52
  # end
53
53
  #
54
- # By default, <tt>ActionDispatch::SystemTestCase</tt> is driven by the
54
+ # By default, +ActionDispatch::SystemTestCase+ is driven by the
55
55
  # Selenium driver, with the Chrome browser, and a browser size of 1400x1400.
56
56
  #
57
57
  # Changing the driver configuration options is easy. Let's say you want to use
@@ -106,8 +106,8 @@ module ActionDispatch
106
106
  # end
107
107
  # end
108
108
  #
109
- # Because <tt>ActionDispatch::SystemTestCase</tt> is a shim between Capybara
110
- # and Rails, any driver that is supported by Capybara is supported by system
109
+ # Because +ActionDispatch::SystemTestCase+ is a shim between Capybara
110
+ # and \Rails, any driver that is supported by Capybara is supported by system
111
111
  # tests as long as you include the required gems and files.
112
112
  class SystemTestCase < ActiveSupport::TestCase
113
113
  include Capybara::DSL
@@ -26,16 +26,15 @@ module ActionDispatch
26
26
  yield options if block_given? && options
27
27
  end
28
28
 
29
- # driver_path can be configured as a proc.
30
- # proc to update web drivers. Running this proc early allows us to only
31
- # update the webdriver once and avoid race conditions when using
32
- # parallel tests.
29
+ # driver_path can be configured as a proc. Running this proc early allows
30
+ # us to only update the webdriver once and avoid race conditions when
31
+ # using parallel tests.
33
32
  def preload
34
33
  case type
35
34
  when :chrome
36
- ::Selenium::WebDriver::Chrome::Service.driver_path&.call
35
+ ::Selenium::WebDriver::Chrome::Service.driver_path.try(:call)
37
36
  when :firefox
38
- ::Selenium::WebDriver::Firefox::Service.driver_path&.call
37
+ ::Selenium::WebDriver::Firefox::Service.driver_path.try(:call)
39
38
  end
40
39
  end
41
40
 
@@ -12,14 +12,6 @@ module ActionDispatch
12
12
  @name = @options.delete(:name) || driver_type
13
13
  @capabilities = capabilities
14
14
 
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
15
  if driver_type == :selenium
24
16
  gem "selenium-webdriver", ">= 4.0.0"
25
17
  require "selenium/webdriver"
@@ -38,7 +30,7 @@ module ActionDispatch
38
30
 
39
31
  private
40
32
  def registerable?
41
- [:selenium, :poltergeist, :webkit, :cuprite, :rack_test].include?(@driver_type)
33
+ [:selenium, :cuprite, :rack_test, :playwright].include?(@driver_type)
42
34
  end
43
35
 
44
36
  def register
@@ -47,10 +39,9 @@ module ActionDispatch
47
39
  Capybara.register_driver name do |app|
48
40
  case @driver_type
49
41
  when :selenium then register_selenium(app)
50
- when :poltergeist then register_poltergeist(app)
51
- when :webkit then register_webkit(app)
52
42
  when :cuprite then register_cuprite(app)
53
43
  when :rack_test then register_rack_test(app)
44
+ when :playwright then register_playwright(app)
54
45
  end
55
46
  end
56
47
  end
@@ -65,16 +56,6 @@ module ActionDispatch
65
56
  end
66
57
  end
67
58
 
68
- def register_poltergeist(app)
69
- Capybara::Poltergeist::Driver.new(app, @options.merge(window_size: @screen_size))
70
- end
71
-
72
- def register_webkit(app)
73
- Capybara::Webkit::Driver.new(app, Capybara::Webkit::Configuration.to_hash.merge(@options)).tap do |driver|
74
- driver.resize_window_to(driver.current_window_handle, *@screen_size)
75
- end
76
- end
77
-
78
59
  def register_cuprite(app)
79
60
  Capybara::Cuprite::Driver.new(app, @options.merge(window_size: @screen_size))
80
61
  end
@@ -83,6 +64,17 @@ module ActionDispatch
83
64
  Capybara::RackTest::Driver.new(app, respect_data_method: true, **@options)
84
65
  end
85
66
 
67
+ def register_playwright(app)
68
+ screen = { width: @screen_size[0], height: @screen_size[1] } if @screen_size
69
+ options = {
70
+ screen: screen,
71
+ viewport: screen,
72
+ **@options
73
+ }.compact
74
+
75
+ Capybara::Playwright::Driver.new(app, **options)
76
+ end
77
+
86
78
  def setup
87
79
  Capybara.current_driver = name
88
80
  end
@@ -13,28 +13,28 @@ module ActionDispatch
13
13
  # to investigate changes at different points during your test. These will be
14
14
  # named with a sequential prefix (or 'failed' for failing tests)
15
15
  #
16
- # The screenshot will be displayed in your console, if supported.
17
- #
18
16
  # The default screenshots directory is +tmp/screenshots+ but you can set a different
19
17
  # one with +Capybara.save_path+
20
18
  #
21
- # You can set the +RAILS_SYSTEM_TESTING_SCREENSHOT_HTML+ environment variable to
22
- # save the HTML from the page that is being screenshotted so you can investigate the
23
- # elements on the page at the time of the screenshot
19
+ # You can use the +html+ argument or set the +RAILS_SYSTEM_TESTING_SCREENSHOT_HTML+
20
+ # environment variable to save the HTML from the page that is being screenshotted
21
+ # so you can investigate the elements on the page at the time of the screenshot
24
22
  #
25
- # You can set the +RAILS_SYSTEM_TESTING_SCREENSHOT+ environment variable to
26
- # control the output. Possible values are:
23
+ # You can use the +screenshot+ argument or set the +RAILS_SYSTEM_TESTING_SCREENSHOT+
24
+ # environment variable to control the output. Possible values are:
27
25
  # * [+simple+ (default)] Only displays the screenshot path.
28
26
  # This is the default value.
29
27
  # * [+inline+] Display the screenshot in the terminal using the
30
28
  # iTerm image protocol (https://iterm2.com/documentation-images.html).
31
29
  # * [+artifact+] Display the screenshot in the terminal, using the terminal
32
30
  # artifact format (https://buildkite.github.io/terminal-to-html/inline-images/).
33
- def take_screenshot
31
+ def take_screenshot(html: false, screenshot: nil)
32
+ showing_html = html || html_from_env?
33
+
34
34
  increment_unique
35
- save_html if save_html?
35
+ save_html if showing_html
36
36
  save_image
37
- puts display_image
37
+ show display_image(html: showing_html, screenshot_output: screenshot)
38
38
  end
39
39
 
40
40
  # Takes a screenshot of the current page in the browser if the test
@@ -42,13 +42,16 @@ module ActionDispatch
42
42
  #
43
43
  # +take_failed_screenshot+ is called during system test teardown.
44
44
  def take_failed_screenshot
45
- take_screenshot if failed? && supports_screenshot? && Capybara::Session.instance_created?
45
+ return unless failed? && supports_screenshot? && Capybara::Session.instance_created?
46
+
47
+ take_screenshot
48
+ metadata[:failure_screenshot_path] = relative_image_path if Minitest::Runnable.method_defined?(:metadata)
46
49
  end
47
50
 
48
51
  private
49
52
  attr_accessor :_screenshot_counter
50
53
 
51
- def save_html?
54
+ def html_from_env?
52
55
  ENV["RAILS_SYSTEM_TESTING_SCREENSHOT_HTML"] == "1"
53
56
  end
54
57
 
@@ -62,7 +65,7 @@ module ActionDispatch
62
65
  end
63
66
 
64
67
  def image_name
65
- sanitized_method_name = method_name.tr("/\\", "--")
68
+ sanitized_method_name = method_name.gsub(/[^\w]+/, "-")
66
69
  name = "#{unique}_#{sanitized_method_name}"
67
70
  name[0...225]
68
71
  end
@@ -87,6 +90,10 @@ module ActionDispatch
87
90
  "#{absolute_path}.png"
88
91
  end
89
92
 
93
+ def relative_image_path
94
+ "#{absolute_path.relative_path_from(Rails.root)}.png"
95
+ end
96
+
90
97
  def absolute_html_path
91
98
  "#{absolute_path}.html"
92
99
  end
@@ -109,11 +116,15 @@ module ActionDispatch
109
116
  output_type
110
117
  end
111
118
 
112
- def display_image
119
+ def show(img)
120
+ puts img
121
+ end
122
+
123
+ def display_image(html:, screenshot_output:)
113
124
  message = +"[Screenshot Image]: #{image_path}\n"
114
- message << +"[Screenshot HTML]: #{html_path}\n" if save_html?
125
+ message << +"[Screenshot HTML]: #{html_path}\n" if html
115
126
 
116
- case output_type
127
+ case screenshot_output || output_type
117
128
  when "artifact"
118
129
  message << "\e]1338;url=artifact://#{absolute_image_path}\a\n"
119
130
  when "inline"
@@ -30,7 +30,7 @@ module ActionDispatch
30
30
  def assert_response(type, message = nil)
31
31
  message ||= generate_response_message(type)
32
32
 
33
- if RESPONSE_PREDICATES.keys.include?(type)
33
+ if RESPONSE_PREDICATES.key?(type)
34
34
  assert @response.public_send(RESPONSE_PREDICATES[type]), message
35
35
  else
36
36
  assert_equal AssertionResponse.new(type).code, @response.response_code, message
@@ -50,12 +50,19 @@ module ActionDispatch
50
50
  #
51
51
  # # Asserts that the redirection matches the regular expression
52
52
  # assert_redirected_to %r(\Ahttp://example.org)
53
- def assert_redirected_to(options = {}, message = nil)
54
- assert_response(:redirect, message)
55
- return true if options === @response.location
53
+ #
54
+ # # Asserts that the redirection has the HTTP status code 301 (Moved
55
+ # # Permanently).
56
+ # assert_redirected_to "/some/path", status: :moved_permanently
57
+ def assert_redirected_to(url_options = {}, options = {}, message = nil)
58
+ options, message = {}, options unless options.is_a?(Hash)
59
+
60
+ status = options[:status] || :redirect
61
+ assert_response(status, message)
62
+ return true if url_options === @response.location
56
63
 
57
64
  redirect_is = normalize_argument_to_redirection(@response.location)
58
- redirect_expected = normalize_argument_to_redirection(options)
65
+ redirect_expected = normalize_argument_to_redirection(url_options)
59
66
 
60
67
  message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
61
68
  assert_operator redirect_expected, :===, redirect_is, message
@@ -93,7 +100,7 @@ module ActionDispatch
93
100
  end
94
101
 
95
102
  def code_with_name(code_or_name)
96
- if RESPONSE_PREDICATES.values.include?("#{code_or_name}?".to_sym)
103
+ if RESPONSE_PREDICATES.value?("#{code_or_name}?".to_sym)
97
104
  code_or_name = RESPONSE_PREDICATES.invert["#{code_or_name}?".to_sym]
98
105
  end
99
106
 
@@ -9,6 +9,36 @@ module ActionDispatch
9
9
  module Assertions
10
10
  # Suite of assertions to test routes generated by \Rails and the handling of requests made to them.
11
11
  module RoutingAssertions
12
+ extend ActiveSupport::Concern
13
+
14
+ module ClassMethods
15
+ # A helper to make it easier to test different route configurations.
16
+ # This method temporarily replaces @routes with a new RouteSet instance
17
+ # before each test.
18
+ #
19
+ # The new instance is yielded to the passed block. Typically the block
20
+ # will create some routes using <tt>set.draw { match ... }</tt>:
21
+ #
22
+ # with_routing do |set|
23
+ # set.draw do
24
+ # resources :users
25
+ # end
26
+ # end
27
+ #
28
+ def with_routing(&block)
29
+ old_routes, old_controller = nil
30
+
31
+ setup do
32
+ old_routes, old_controller = @routes, @controller
33
+ create_routes(&block)
34
+ end
35
+
36
+ teardown do
37
+ reset_routes(old_routes, old_controller)
38
+ end
39
+ end
40
+ end
41
+
12
42
  def setup # :nodoc:
13
43
  @routes ||= nil
14
44
  super
@@ -83,7 +113,7 @@ module ActionDispatch
83
113
  # # Asserts that the generated route gives us our custom route
84
114
  # assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" }
85
115
  def assert_generates(expected_path, options, defaults = {}, extras = {}, message = nil)
86
- if %r{://}.match?(expected_path)
116
+ if expected_path.include?("://")
87
117
  fail_on(URI::InvalidURIError, message) do
88
118
  uri = URI.parse(expected_path)
89
119
  expected_path = uri.path.to_s.empty? ? "/" : uri.path
@@ -150,33 +180,11 @@ module ActionDispatch
150
180
  # assert_equal "/users", users_path
151
181
  # end
152
182
  #
153
- def with_routing
154
- old_routes, @routes = @routes, ActionDispatch::Routing::RouteSet.new
155
- if defined?(@controller) && @controller
156
- old_controller, @controller = @controller, @controller.clone
157
- _routes = @routes
158
-
159
- @controller.singleton_class.include(_routes.url_helpers)
160
-
161
- if @controller.respond_to? :view_context_class
162
- view_context_class = Class.new(@controller.view_context_class) do
163
- include _routes.url_helpers
164
- end
165
-
166
- custom_view_context = Module.new {
167
- define_method(:view_context_class) do
168
- view_context_class
169
- end
170
- }
171
- @controller.extend(custom_view_context)
172
- end
173
- end
174
- yield @routes
183
+ def with_routing(&block)
184
+ old_routes, old_controller = @routes, @controller
185
+ create_routes(&block)
175
186
  ensure
176
- @routes = old_routes
177
- if defined?(@controller) && @controller
178
- @controller = old_controller
179
- end
187
+ reset_routes(old_routes, old_controller)
180
188
  end
181
189
 
182
190
  # ROUTES TODO: These assertions should really work in an integration context
@@ -190,6 +198,37 @@ module ActionDispatch
190
198
  ruby2_keywords(:method_missing)
191
199
 
192
200
  private
201
+ def create_routes
202
+ @routes = ActionDispatch::Routing::RouteSet.new
203
+ if defined?(@controller) && @controller
204
+ @controller = @controller.clone
205
+ _routes = @routes
206
+
207
+ @controller.singleton_class.include(_routes.url_helpers)
208
+
209
+ if @controller.respond_to? :view_context_class
210
+ view_context_class = Class.new(@controller.view_context_class) do
211
+ include _routes.url_helpers
212
+ end
213
+
214
+ custom_view_context = Module.new {
215
+ define_method(:view_context_class) do
216
+ view_context_class
217
+ end
218
+ }
219
+ @controller.extend(custom_view_context)
220
+ end
221
+ end
222
+ yield @routes
223
+ end
224
+
225
+ def reset_routes(old_routes, old_controller)
226
+ @routes = old_routes
227
+ if defined?(@controller) && @controller
228
+ @controller = old_controller
229
+ end
230
+ end
231
+
193
232
  # Recognizes the route for a given path.
194
233
  def recognized_request_for(path, extras = {}, msg)
195
234
  if path.is_a?(Hash)
@@ -202,7 +241,7 @@ module ActionDispatch
202
241
  controller = @controller if defined?(@controller)
203
242
  request = ActionController::TestRequest.create controller&.class
204
243
 
205
- if %r{://}.match?(path)
244
+ if path.include?("://")
206
245
  fail_on(URI::InvalidURIError, msg) do
207
246
  uri = URI.parse(path)
208
247
  request.env["rack.url_scheme"] = uri.scheme || "http"
@@ -6,6 +6,8 @@ require "action_dispatch/testing/assertions/routing"
6
6
 
7
7
  module ActionDispatch
8
8
  module Assertions
9
+ extend ActiveSupport::Concern
10
+
9
11
  include ResponseAssertions
10
12
  include RoutingAssertions
11
13
  include Rails::Dom::Testing::Assertions
@@ -14,7 +16,7 @@ module ActionDispatch
14
16
  @html_document ||= if @response.media_type&.end_with?("xml")
15
17
  Nokogiri::XML::Document.parse(@response.body)
16
18
  else
17
- Nokogiri::HTML::Document.parse(@response.body)
19
+ Rails::Dom::Testing.html_document.parse(@response.body)
18
20
  end
19
21
  end
20
22
  end