rspec-rails 4.0.0 → 6.1.2

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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/Changelog.md +247 -53
  4. data/README.md +73 -61
  5. data/lib/generators/rspec/channel/channel_generator.rb +1 -1
  6. data/lib/generators/rspec/controller/controller_generator.rb +5 -5
  7. data/lib/generators/rspec/controller/templates/request_spec.rb +6 -1
  8. data/lib/generators/rspec/feature/feature_generator.rb +1 -1
  9. data/lib/generators/rspec/generator/generator_generator.rb +3 -3
  10. data/lib/generators/rspec/generator/templates/generator_spec.rb +1 -2
  11. data/lib/generators/rspec/helper/helper_generator.rb +1 -1
  12. data/lib/generators/rspec/install/install_generator.rb +19 -2
  13. data/lib/generators/rspec/install/templates/spec/rails_helper.rb +13 -8
  14. data/lib/generators/rspec/integration/integration_generator.rb +10 -3
  15. data/lib/generators/rspec/job/job_generator.rb +2 -1
  16. data/lib/generators/rspec/job/templates/job_spec.rb.erb +1 -1
  17. data/lib/generators/rspec/mailbox/mailbox_generator.rb +1 -1
  18. data/lib/generators/rspec/mailer/mailer_generator.rb +5 -3
  19. data/lib/generators/rspec/mailer/templates/mailer_spec.rb +2 -2
  20. data/lib/generators/rspec/mailer/templates/preview.rb +2 -2
  21. data/lib/generators/rspec/model/model_generator.rb +3 -3
  22. data/lib/generators/rspec/request/request_generator.rb +10 -3
  23. data/lib/generators/rspec/scaffold/scaffold_generator.rb +8 -4
  24. data/lib/generators/rspec/scaffold/templates/api_controller_spec.rb +13 -13
  25. data/lib/generators/rspec/scaffold/templates/api_request_spec.rb +20 -20
  26. data/lib/generators/rspec/scaffold/templates/controller_spec.rb +25 -58
  27. data/lib/generators/rspec/scaffold/templates/edit_spec.rb +8 -8
  28. data/lib/generators/rspec/scaffold/templates/index_spec.rb +2 -1
  29. data/lib/generators/rspec/scaffold/templates/new_spec.rb +1 -5
  30. data/lib/generators/rspec/scaffold/templates/request_spec.rb +38 -18
  31. data/lib/generators/rspec/scaffold/templates/show_spec.rb +1 -1
  32. data/lib/generators/rspec/system/system_generator.rb +14 -16
  33. data/lib/generators/rspec/view/view_generator.rb +2 -2
  34. data/lib/generators/rspec.rb +18 -1
  35. data/lib/rspec/rails/adapters.rb +11 -0
  36. data/lib/rspec/rails/configuration.rb +82 -18
  37. data/lib/rspec/rails/example/controller_example_group.rb +1 -0
  38. data/lib/rspec/rails/example/mailbox_example_group.rb +2 -2
  39. data/lib/rspec/rails/example/mailer_example_group.rb +2 -2
  40. data/lib/rspec/rails/example/rails_example_group.rb +8 -0
  41. data/lib/rspec/rails/example/request_example_group.rb +1 -4
  42. data/lib/rspec/rails/example/routing_example_group.rb +0 -2
  43. data/lib/rspec/rails/example/system_example_group.rb +63 -15
  44. data/lib/rspec/rails/example/view_example_group.rb +6 -5
  45. data/lib/rspec/rails/extensions/active_record/proxy.rb +4 -1
  46. data/lib/rspec/rails/feature_check.rb +6 -2
  47. data/lib/rspec/rails/file_fixture_support.rb +11 -10
  48. data/lib/rspec/rails/fixture_file_upload_support.rb +19 -14
  49. data/lib/rspec/rails/fixture_support.rb +49 -24
  50. data/lib/rspec/rails/matchers/action_cable/have_broadcasted_to.rb +6 -3
  51. data/lib/rspec/rails/matchers/action_mailbox.rb +14 -5
  52. data/lib/rspec/rails/matchers/active_job.rb +32 -6
  53. data/lib/rspec/rails/matchers/have_enqueued_mail.rb +34 -5
  54. data/lib/rspec/rails/matchers/have_http_status.rb +5 -5
  55. data/lib/rspec/rails/matchers/relation_match_array.rb +1 -1
  56. data/lib/rspec/rails/matchers/routing_matchers.rb +2 -2
  57. data/lib/rspec/rails/matchers/send_email.rb +122 -0
  58. data/lib/rspec/rails/matchers.rb +1 -0
  59. data/lib/rspec/rails/vendor/capybara.rb +1 -3
  60. data/lib/rspec/rails/version.rb +1 -1
  61. data/lib/rspec/rails/view_assigns.rb +0 -18
  62. data/lib/rspec/rails/view_rendering.rb +13 -11
  63. data/lib/rspec-rails.rb +13 -11
  64. data.tar.gz.sig +0 -0
  65. metadata +28 -27
  66. metadata.gz.sig +0 -0
  67. /data/lib/generators/rspec/{integration → request}/templates/request_spec.rb +0 -0
@@ -13,7 +13,7 @@ module RSpec
13
13
  def self.create_inbound_email(arg)
14
14
  case arg
15
15
  when Hash
16
- create_inbound_email_from_mail(arg)
16
+ create_inbound_email_from_mail(**arg)
17
17
  else
18
18
  create_inbound_email_from_source(arg.to_s)
19
19
  end
@@ -69,7 +69,7 @@ module RSpec
69
69
  #
70
70
  # @param message [Hash, Mail::Message] a mail message or hash of
71
71
  # attributes used to build one
72
- # @return [ActionMaibox::InboundMessage]
72
+ # @return [ActionMailbox::InboundMessage]
73
73
  def process(message)
74
74
  MailboxExampleGroup.create_inbound_email(message).tap do |mail|
75
75
  self.class.mailbox_class.receive(mail)
@@ -21,8 +21,8 @@ if defined?(ActionMailer)
21
21
 
22
22
  included do
23
23
  include ::Rails.application.routes.url_helpers
24
- options = ::Rails.configuration.action_mailer.default_url_options
25
- options&.each { |key, value| default_url_options[key] = value }
24
+ options = ::Rails.configuration.action_mailer.default_url_options || {}
25
+ options.each { |key, value| default_url_options[key] = value }
26
26
  end
27
27
 
28
28
  # Class-level DSL for mailer specs.
@@ -2,6 +2,10 @@
2
2
  # suite and ammeter.
3
3
  require 'rspec/rails/matchers'
4
4
 
5
+ if ::Rails::VERSION::MAJOR >= 7
6
+ require 'active_support/execution_context/test_helper'
7
+ end
8
+
5
9
  module RSpec
6
10
  module Rails
7
11
  # @api public
@@ -12,6 +16,10 @@ module RSpec
12
16
  include RSpec::Rails::MinitestLifecycleAdapter
13
17
  include RSpec::Rails::MinitestAssertionAdapter
14
18
  include RSpec::Rails::FixtureSupport
19
+ if ::Rails::VERSION::MAJOR >= 7
20
+ include RSpec::Rails::TaggedLoggingAdapter
21
+ include ActiveSupport::ExecutionContext::TestHelper
22
+ end
15
23
  end
16
24
  end
17
25
  end
@@ -10,10 +10,7 @@ module RSpec
10
10
  include RSpec::Rails::Matchers::RedirectTo
11
11
  include RSpec::Rails::Matchers::RenderTemplate
12
12
  include ActionController::TemplateAssertions
13
-
14
- if ActionPack::VERSION::MAJOR >= 5
15
- include ActionDispatch::IntegrationTest::Behavior
16
- end
13
+ include ActionDispatch::IntegrationTest::Behavior
17
14
 
18
15
  # Delegates to `Rails.application`.
19
16
  def app
@@ -1,5 +1,3 @@
1
- require "action_dispatch/testing/assertions/routing"
2
-
3
1
  module RSpec
4
2
  module Rails
5
3
  # @private
@@ -41,7 +41,53 @@ module RSpec
41
41
  @method_name ||= [
42
42
  self.class.name.underscore,
43
43
  RSpec.current_example.description.underscore
44
- ].join("_").tr(CHARS_TO_TRANSLATE.join, "_")[0...200] + "_#{rand(1000)}"
44
+ ].join("_").tr(CHARS_TO_TRANSLATE.join, "_").byteslice(0...200).scrub("") + "_#{rand(1000)}"
45
+ end
46
+
47
+ if ::Rails::VERSION::STRING.to_f >= 7.1
48
+ # @private
49
+ # Allows failure screenshot to work whilst not exposing metadata
50
+ class SuppressRailsScreenshotMetadata
51
+ def initialize
52
+ @example_data = {}
53
+ end
54
+
55
+ def [](key)
56
+ if @example_data.key?(key)
57
+ @example_data[key]
58
+ else
59
+ raise_wrong_scope_error
60
+ end
61
+ end
62
+
63
+ def []=(key, value)
64
+ if key == :failure_screenshot_path
65
+ @example_data[key] = value
66
+ else
67
+ raise_wrong_scope_error
68
+ end
69
+ end
70
+
71
+ def method_missing(_name, *_args, &_block)
72
+ raise_wrong_scope_error
73
+ end
74
+
75
+ private
76
+
77
+ def raise_wrong_scope_error
78
+ raise RSpec::Core::ExampleGroup::WrongScopeError,
79
+ "`metadata` is not available from within an example " \
80
+ "(e.g. an `it` block) or from constructs that run in the " \
81
+ "scope of an example (e.g. `before`, `let`, etc). It is " \
82
+ "only available on an example group (e.g. a `describe` or "\
83
+ "`context` block)"
84
+ end
85
+ end
86
+
87
+ # @private
88
+ def metadata
89
+ @metadata ||= SuppressRailsScreenshotMetadata.new
90
+ end
45
91
  end
46
92
 
47
93
  # Delegates to `Rails.application`.
@@ -54,23 +100,22 @@ module RSpec
54
100
  ActionDispatch::SystemTesting::Server.silence_puma = true
55
101
  end
56
102
 
103
+ require 'action_dispatch/system_test_case'
104
+
57
105
  begin
58
106
  require 'capybara'
59
- require 'action_dispatch/system_test_case'
60
107
  rescue LoadError => e
61
108
  abort """
62
109
  LoadError: #{e.message}
63
- System test integration requires Rails >= 5.1 and has a hard
110
+ System test integration has a hard
64
111
  dependency on a webserver and `capybara`, please add capybara to
65
112
  your Gemfile and configure a webserver (e.g. `Capybara.server =
66
- :webrick`) before attempting to use system specs.
113
+ :puma`) before attempting to use system specs.
67
114
  """.gsub(/\s+/, ' ').strip
68
115
  end
69
116
 
70
- if ::Rails::VERSION::STRING >= '6.0'
71
- original_before_teardown =
72
- ::ActionDispatch::SystemTesting::TestHelpers::SetupAndTeardown.instance_method(:before_teardown)
73
- end
117
+ original_before_teardown =
118
+ ::ActionDispatch::SystemTesting::TestHelpers::SetupAndTeardown.instance_method(:before_teardown)
74
119
 
75
120
  original_after_teardown =
76
121
  ::ActionDispatch::SystemTesting::TestHelpers::SetupAndTeardown.instance_method(:after_teardown)
@@ -96,8 +141,8 @@ module RSpec
96
141
  end
97
142
  end
98
143
 
99
- def driven_by(*args, &blk)
100
- @driver = ::ActionDispatch::SystemTestCase.driven_by(*args, &blk).tap(&:use)
144
+ def driven_by(driver, **driver_options, &blk)
145
+ @driver = ::ActionDispatch::SystemTestCase.driven_by(driver, **driver_options, &blk).tap(&:use)
101
146
  end
102
147
 
103
148
  before do
@@ -108,16 +153,19 @@ module RSpec
108
153
  orig_stdout = $stdout
109
154
  $stdout = StringIO.new
110
155
  begin
111
- if ::Rails::VERSION::STRING >= '6.0'
112
- original_before_teardown.bind(self).call
113
- end
114
- original_after_teardown.bind(self).call
156
+ original_before_teardown.bind(self).call
115
157
  ensure
116
158
  myio = $stdout
117
- RSpec.current_example.metadata[:extra_failure_lines] = myio.string
159
+ myio.rewind
160
+ RSpec.current_example.metadata[:extra_failure_lines] = myio.readlines
118
161
  $stdout = orig_stdout
119
162
  end
120
163
  end
164
+
165
+ around do |example|
166
+ example.run
167
+ original_after_teardown.bind(self).call
168
+ end
121
169
  end
122
170
  end
123
171
  end
@@ -65,6 +65,7 @@ module RSpec
65
65
  # end
66
66
  def render(options = {}, local_assigns = {}, &block)
67
67
  options = _default_render_options if Hash === options && options.empty?
68
+ options = options.merge(_default_render_options) if Hash === options && options.keys == [:locals]
68
69
  super(options, local_assigns, &block)
69
70
  end
70
71
 
@@ -89,7 +90,7 @@ module RSpec
89
90
  #
90
91
  # stub_template("widgets/_widget.html.erb" => "This content.")
91
92
  def stub_template(hash)
92
- view.view_paths.unshift(StubResolverCache.resolver_for(hash))
93
+ controller.prepend_view_path(StubResolverCache.resolver_for(hash))
93
94
  end
94
95
 
95
96
  # Provides access to the params hash that will be available within the
@@ -149,11 +150,11 @@ module RSpec
149
150
  # the original string.
150
151
  match = path_regex.match(_default_file_to_render)
151
152
 
152
- render_options = {template: match[:template]}
153
- render_options[:handlers] = [match[:handler]] if match[:handler]
153
+ render_options = { template: match[:template] }
154
+ render_options[:handlers] = [match[:handler].to_sym] if match[:handler]
154
155
  render_options[:formats] = [match[:format].to_sym] if match[:format]
155
- render_options[:locales] = [match[:locale]] if match[:locale]
156
- render_options[:variants] = [match[:variant]] if match[:variant]
156
+ render_options[:locales] = [match[:locale].to_sym] if match[:locale]
157
+ render_options[:variants] = [match[:variant].to_sym] if match[:variant]
157
158
 
158
159
  render_options
159
160
  end
@@ -1,7 +1,10 @@
1
1
  RSpec.configure do |rspec|
2
2
  # Delay this in order to give users a chance to configure `expect_with`...
3
3
  rspec.before(:suite) do
4
- if defined?(RSpec::Matchers) && RSpec::Matchers.configuration.syntax.include?(:should) && defined?(ActiveRecord::Associations)
4
+ if defined?(RSpec::Matchers) &&
5
+ RSpec::Matchers.configuration.respond_to?(:syntax) && # RSpec 4 dropped support for monkey-patching `should` syntax
6
+ RSpec::Matchers.configuration.syntax.include?(:should) &&
7
+ defined?(ActiveRecord::Associations)
5
8
  RSpec::Matchers.configuration.add_should_and_should_not_to ActiveRecord::Associations::CollectionProxy
6
9
  end
7
10
  end
@@ -24,17 +24,21 @@ module RSpec
24
24
  end
25
25
 
26
26
  def has_action_cable_testing?
27
- defined?(::ActionCable) && ActionCable::VERSION::MAJOR >= 6
27
+ defined?(::ActionCable)
28
28
  end
29
29
 
30
30
  def has_action_mailer_parameterized?
31
- has_action_mailer? && defined?(::ActionMailer::Parameterized)
31
+ has_action_mailer? && defined?(::ActionMailer::Parameterized::DeliveryJob)
32
32
  end
33
33
 
34
34
  def has_action_mailer_unified_delivery?
35
35
  has_action_mailer? && defined?(::ActionMailer::MailDeliveryJob)
36
36
  end
37
37
 
38
+ def has_action_mailer_legacy_delivery_job?
39
+ defined?(ActionMailer::DeliveryJob)
40
+ end
41
+
38
42
  def has_action_mailbox?
39
43
  defined?(::ActionMailbox)
40
44
  end
@@ -1,15 +1,16 @@
1
- if ::Rails::VERSION::STRING > '5'
2
- require 'active_support/testing/file_fixtures'
1
+ require 'active_support/testing/file_fixtures'
3
2
 
4
- module RSpec
5
- module Rails
6
- # @private
7
- module FileFixtureSupport
8
- extend ActiveSupport::Concern
9
- include ActiveSupport::Testing::FileFixtures
3
+ module RSpec
4
+ module Rails
5
+ # @private
6
+ module FileFixtureSupport
7
+ extend ActiveSupport::Concern
8
+ include ActiveSupport::Testing::FileFixtures
10
9
 
11
- included do
12
- self.file_fixture_path = RSpec.configuration.file_fixture_path
10
+ included do
11
+ self.file_fixture_path = RSpec.configuration.file_fixture_path
12
+ if defined?(ActiveStorage::FixtureSet)
13
+ ActiveStorage::FixtureSet.file_fixture_path = RSpec.configuration.file_fixture_path
13
14
  end
14
15
  end
15
16
  end
@@ -6,33 +6,38 @@ module RSpec
6
6
 
7
7
  private
8
8
 
9
+ # In Rails 7.0 fixture file path needs to be relative to `file_fixture_path` instead, this change
10
+ # was brought in with a deprecation warning on 6.1. In Rails 7.0 expect to rework this to remove
11
+ # the old accessor.
9
12
  def rails_fixture_file_wrapper
10
- RailsFixtureFileWrapper.fixture_path = nil
11
- resolved_fixture_path = (fixture_path || RSpec.configuration.fixture_path || '').to_s
12
- RailsFixtureFileWrapper.fixture_path = File.join(resolved_fixture_path, '') unless resolved_fixture_path.strip.empty?
13
+ RailsFixtureFileWrapper.file_fixture_path = nil
14
+ resolved_fixture_path =
15
+ if respond_to?(:file_fixture_path) && !file_fixture_path.nil?
16
+ file_fixture_path.to_s
17
+ elsif respond_to?(:fixture_paths)
18
+ (RSpec.configuration.fixture_paths&.first || '').to_s
19
+ else
20
+ (RSpec.configuration.fixture_path || '').to_s
21
+ end
22
+ RailsFixtureFileWrapper.file_fixture_path = File.join(resolved_fixture_path, '') unless resolved_fixture_path.strip.empty?
13
23
  RailsFixtureFileWrapper.instance
14
24
  end
15
25
 
16
26
  class RailsFixtureFileWrapper
17
27
  include ActionDispatch::TestProcess if defined?(ActionDispatch::TestProcess)
28
+ include ActiveSupport::Testing::FileFixtures
18
29
 
19
30
  class << self
20
- attr_reader :fixture_path
31
+ if ::Rails::VERSION::STRING < "7.1.0"
32
+ attr_accessor :fixture_path
33
+ else
34
+ attr_accessor :fixture_paths
35
+ end
21
36
 
22
37
  # Get instance of wrapper
23
38
  def instance
24
39
  @instance ||= new
25
40
  end
26
-
27
- # Override fixture_path set
28
- # to support Rails 3.0->3.1 using ActionController::TestCase class to resolve fixture_path
29
- # see https://apidock.com/rails/v3.0.0/ActionDispatch/TestProcess/fixture_file_upload
30
- def fixture_path=(value)
31
- if ActionController::TestCase.respond_to?(:fixture_path)
32
- ActionController::TestCase.fixture_path = value
33
- end
34
- @fixture_path = value
35
- end
36
41
  end
37
42
  end
38
43
  end
@@ -9,16 +9,26 @@ module RSpec
9
9
  include RSpec::Rails::MinitestAssertionAdapter
10
10
  include ActiveRecord::TestFixtures
11
11
 
12
+ # @private prevent ActiveSupport::TestFixtures to start a DB transaction.
13
+ # Monkey patched to avoid collisions with 'let(:name)' in Rails 6.1 and after
14
+ # and let(:method_name) before Rails 6.1.
15
+ def run_in_transaction?
16
+ current_example_name = (RSpec.current_example && RSpec.current_example.metadata[:description])
17
+ use_transactional_tests && !self.class.uses_transaction?(current_example_name)
18
+ end
19
+
12
20
  included do
13
21
  if RSpec.configuration.use_active_record?
14
22
  include Fixtures
15
23
 
16
- self.fixture_path = RSpec.configuration.fixture_path
17
- if ::Rails::VERSION::STRING > '5'
18
- self.use_transactional_tests = RSpec.configuration.use_transactional_fixtures
24
+ # TestFixtures#fixture_path is deprecated and will be removed in Rails 7.2
25
+ if respond_to?(:fixture_paths=)
26
+ self.fixture_paths = RSpec.configuration.fixture_paths
19
27
  else
20
- self.use_transactional_fixtures = RSpec.configuration.use_transactional_fixtures
28
+ self.fixture_path = RSpec.configuration.fixture_path
21
29
  end
30
+
31
+ self.use_transactional_tests = RSpec.configuration.use_transactional_fixtures
22
32
  self.use_instantiated_fixtures = RSpec.configuration.use_instantiated_fixtures
23
33
 
24
34
  fixtures RSpec.configuration.global_fixtures if RSpec.configuration.global_fixtures
@@ -28,35 +38,50 @@ module RSpec
28
38
  module Fixtures
29
39
  extend ActiveSupport::Concern
30
40
 
41
+ # rubocop:disable Metrics/BlockLength
31
42
  class_methods do
32
- def fixtures(*args)
33
- orig_methods = private_instance_methods
34
- super.tap do
35
- new_methods = private_instance_methods - orig_methods
36
- new_methods.each do |method_name|
37
- proxy_method_warning_if_called_in_before_context_scope(method_name)
43
+ if ::Rails.version.to_f >= 7.1
44
+ def fixtures(*args)
45
+ super.tap do
46
+ fixture_sets.each_pair do |method_name, fixture_name|
47
+ proxy_method_warning_if_called_in_before_context_scope(method_name, fixture_name)
48
+ end
38
49
  end
39
50
  end
40
- end
41
51
 
42
- def proxy_method_warning_if_called_in_before_context_scope(method_name)
43
- orig_implementation = instance_method(method_name)
44
- define_method(method_name) do |*args, &blk|
45
- if inspect.include?("before(:context)")
46
- RSpec.warn_with("Calling fixture method in before :context ")
47
- else
48
- orig_implementation.bind(self).call(*args, &blk)
52
+ def proxy_method_warning_if_called_in_before_context_scope(method_name, fixture_name)
53
+ define_method(method_name) do |*args, **kwargs, &blk|
54
+ if RSpec.current_scope == :before_context_hook
55
+ RSpec.warn_with("Calling fixture method in before :context ")
56
+ else
57
+ access_fixture(fixture_name, *args, **kwargs, &blk)
58
+ end
59
+ end
60
+ end
61
+ else
62
+ def fixtures(*args)
63
+ orig_methods = private_instance_methods
64
+ super.tap do
65
+ new_methods = private_instance_methods - orig_methods
66
+ new_methods.each do |method_name|
67
+ proxy_method_warning_if_called_in_before_context_scope(method_name)
68
+ end
49
69
  end
50
70
  end
51
- end
52
- end
53
71
 
54
- if ::Rails.version.to_f >= 6.1
55
- # @private return the example name for TestFixtures
56
- def name
57
- @example
72
+ def proxy_method_warning_if_called_in_before_context_scope(method_name)
73
+ orig_implementation = instance_method(method_name)
74
+ define_method(method_name) do |*args, &blk|
75
+ if RSpec.current_scope == :before_context_hook
76
+ RSpec.warn_with("Calling fixture method in before :context ")
77
+ else
78
+ orig_implementation.bind(self).call(*args, &blk)
79
+ end
80
+ end
81
+ end
58
82
  end
59
83
  end
84
+ # rubocop:enable Metrics/BlockLength
60
85
  end
61
86
  end
62
87
  end
@@ -96,8 +96,11 @@ module RSpec
96
96
  private
97
97
 
98
98
  def stream
99
- @stream ||= if @target.is_a?(String)
99
+ @stream ||= case @target
100
+ when String
100
101
  @target
102
+ when Symbol
103
+ @target.to_s
101
104
  else
102
105
  check_channel_presence
103
106
  @channel.broadcasting_for(@target)
@@ -109,7 +112,7 @@ module RSpec
109
112
  decoded = ActiveSupport::JSON.decode(msg)
110
113
  decoded = decoded.with_indifferent_access if decoded.is_a?(Hash)
111
114
 
112
- if @data.nil? || @data === decoded
115
+ if @data.nil? || values_match?(@data, decoded)
113
116
  @block.call(decoded)
114
117
  true
115
118
  else
@@ -159,7 +162,7 @@ module RSpec
159
162
  def check_channel_presence
160
163
  return if @channel.present? && @channel.respond_to?(:channel_name)
161
164
 
162
- error_msg = "Broadcasting channel can't be infered. Please, specify it with `from_channel`"
165
+ error_msg = "Broadcasting channel can't be inferred. Please, specify it with `from_channel`"
163
166
  raise ArgumentError, error_msg
164
167
  end
165
168
  end
@@ -22,11 +22,20 @@ module RSpec
22
22
  @inbound_email = create_inbound_email(message)
23
23
  end
24
24
 
25
- def matches?(mailbox)
26
- @mailbox = mailbox
27
- @receiver = ApplicationMailbox.router.send(:match_to_mailbox, inbound_email)
25
+ if defined?(::ApplicationMailbox) && ::ApplicationMailbox.router.respond_to?(:mailbox_for)
26
+ def matches?(mailbox)
27
+ @mailbox = mailbox
28
+ @receiver = ApplicationMailbox.router.mailbox_for(inbound_email)
28
29
 
29
- @receiver == @mailbox
30
+ @receiver == @mailbox
31
+ end
32
+ else
33
+ def matches?(mailbox)
34
+ @mailbox = mailbox
35
+ @receiver = ApplicationMailbox.router.send(:match_to_mailbox, inbound_email)
36
+
37
+ @receiver == @mailbox
38
+ end
30
39
  end
31
40
 
32
41
  def failure_message
@@ -41,7 +50,7 @@ module RSpec
41
50
  "expected #{describe_inbound_email} not to route to #{mailbox}"
42
51
  end
43
52
 
44
- private
53
+ private
45
54
 
46
55
  attr_reader :inbound_email, :mailbox, :receiver
47
56
 
@@ -30,8 +30,12 @@ module RSpec
30
30
  self
31
31
  end
32
32
 
33
- def at(date)
34
- @at = date
33
+ def at(time_or_date)
34
+ case time_or_date
35
+ when Time then @at = Time.at(time_or_date.to_f)
36
+ else
37
+ @at = time_or_date
38
+ end
35
39
  self
36
40
  end
37
41
 
@@ -159,7 +163,29 @@ module RSpec
159
163
  return job[:at].nil? if @at == :no_wait
160
164
  return false unless job[:at]
161
165
 
162
- values_match?(@at, Time.at(job[:at]))
166
+ scheduled_at = Time.at(job[:at])
167
+ values_match?(@at, scheduled_at) || check_for_inprecise_value(scheduled_at)
168
+ end
169
+
170
+ def check_for_inprecise_value(scheduled_at)
171
+ return unless Time === @at && values_match?(@at.change(usec: 0), scheduled_at)
172
+
173
+ RSpec.warn_with((<<-WARNING).gsub(/^\s+\|/, '').chomp)
174
+ |[WARNING] Your expected `at(...)` value does not match the job scheduled_at value
175
+ |unless microseconds are removed. This precision error often occurs when checking
176
+ |values against `Time.current` / `Time.now` which have usec precision, but Rails
177
+ |uses `n.seconds.from_now` internally which has a usec count of `0`.
178
+ |
179
+ |Use `change(usec: 0)` to correct these values. For example:
180
+ |
181
+ |`Time.current.change(usec: 0)`
182
+ |
183
+ |Note: RSpec cannot do this for you because jobs can be scheduled with usec
184
+ |precision and we do not know whether it is on purpose or not.
185
+ |
186
+ |
187
+ WARNING
188
+ false
163
189
  end
164
190
 
165
191
  def set_expected_number(relativity, count)
@@ -204,11 +230,11 @@ module RSpec
204
230
  def matches?(proc)
205
231
  raise ArgumentError, "have_enqueued_job and enqueue_job only support block expectations" unless Proc === proc
206
232
 
207
- original_enqueued_jobs_count = queue_adapter.enqueued_jobs.count
233
+ original_enqueued_jobs = Set.new(queue_adapter.enqueued_jobs)
208
234
  proc.call
209
- in_block_jobs = queue_adapter.enqueued_jobs.drop(original_enqueued_jobs_count)
235
+ enqueued_jobs = Set.new(queue_adapter.enqueued_jobs)
210
236
 
211
- check(in_block_jobs)
237
+ check(enqueued_jobs - original_enqueued_jobs)
212
238
  end
213
239
 
214
240
  def does_not_match?(proc)
@@ -4,6 +4,7 @@
4
4
  require "rspec/mocks/argument_matchers"
5
5
  require "rspec/rails/matchers/active_job"
6
6
 
7
+ # rubocop: disable Metrics/ClassLength
7
8
  module RSpec
8
9
  module Rails
9
10
  module Matchers
@@ -119,9 +120,11 @@ module RSpec
119
120
  end
120
121
 
121
122
  def mail_job_message(job)
122
- mailer_method = job[:args][0..1].join('.')
123
+ job_args = deserialize_arguments(job)
124
+
125
+ mailer_method = job_args[0..1].join('.')
126
+ mailer_args = job_args[3..-1]
123
127
 
124
- mailer_args = job[:args][3..-1]
125
128
  msg_parts = []
126
129
  msg_parts << "with #{mailer_args}" if mailer_args.any?
127
130
  msg_parts << "on queue #{job[:queue]}" if job[:queue] && job[:queue] != 'mailers'
@@ -130,18 +133,43 @@ module RSpec
130
133
  "#{mailer_method} #{msg_parts.join(', ')}".strip
131
134
  end
132
135
 
136
+ # Ruby 3.1 changed how params were serialized on Rails 6.1
137
+ # so we override the active job implementation and customize it here.
138
+ def deserialize_arguments(job)
139
+ args = super
140
+
141
+ return args unless Hash === args.last
142
+
143
+ hash = args.pop
144
+
145
+ if hash.key?("_aj_ruby2_keywords")
146
+ keywords = hash["_aj_ruby2_keywords"]
147
+
148
+ original_hash = keywords.each_with_object({}) { |keyword, new_hash| new_hash[keyword.to_sym] = hash[keyword] }
149
+
150
+ args + [original_hash]
151
+ elsif hash.key?(:args) && hash.key?(:params)
152
+ args + [hash]
153
+ elsif hash.key?(:args)
154
+ args + hash[:args]
155
+ else
156
+ args + [hash]
157
+ end
158
+ end
159
+
133
160
  def legacy_mail?(job)
134
- job[:job] == ActionMailer::DeliveryJob
161
+ RSpec::Rails::FeatureCheck.has_action_mailer_legacy_delivery_job? && job[:job] <= ActionMailer::DeliveryJob
135
162
  end
136
163
 
137
164
  def parameterized_mail?(job)
138
- RSpec::Rails::FeatureCheck.has_action_mailer_parameterized? && job[:job] == ActionMailer::Parameterized::DeliveryJob
165
+ RSpec::Rails::FeatureCheck.has_action_mailer_parameterized? && job[:job] <= ActionMailer::Parameterized::DeliveryJob
139
166
  end
140
167
 
141
168
  def unified_mail?(job)
142
- RSpec::Rails::FeatureCheck.has_action_mailer_unified_delivery? && job[:job] == ActionMailer::MailDeliveryJob
169
+ RSpec::Rails::FeatureCheck.has_action_mailer_unified_delivery? && job[:job] <= ActionMailer::MailDeliveryJob
143
170
  end
144
171
  end
172
+
145
173
  # @api public
146
174
  # Passes if an email has been enqueued inside block.
147
175
  # May chain with to specify expected arguments.
@@ -196,3 +224,4 @@ module RSpec
196
224
  end
197
225
  end
198
226
  end
227
+ # rubocop: enable Metrics/ClassLength