rspec-rails 6.0.3 → 7.1.1
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/Changelog.md +309 -181
- data/README.md +31 -31
- data/lib/generators/rspec/generator/generator_generator.rb +2 -2
- data/lib/generators/rspec/generator/templates/generator_spec.rb +1 -2
- data/lib/generators/rspec/install/templates/spec/rails_helper.rb +25 -12
- data/lib/generators/rspec/mailer/mailer_generator.rb +4 -2
- data/lib/generators/rspec/mailer/templates/preview.rb +3 -3
- data/lib/generators/rspec/scaffold/templates/controller_spec.rb +0 -15
- data/lib/generators/rspec/scaffold/templates/edit_spec.rb +3 -3
- data/lib/generators/rspec/scaffold/templates/index_spec.rb +1 -1
- data/lib/generators/rspec/scaffold/templates/new_spec.rb +1 -1
- data/lib/generators/rspec/scaffold/templates/request_spec.rb +0 -15
- data/lib/generators/rspec/scaffold/templates/show_spec.rb +1 -1
- data/lib/rspec/rails/configuration.rb +31 -2
- data/lib/rspec/rails/example/mailbox_example_group.rb +1 -1
- data/lib/rspec/rails/example/rails_example_group.rb +6 -1
- data/lib/rspec/rails/example/routing_example_group.rb +0 -2
- data/lib/rspec/rails/example/system_example_group.rb +55 -1
- data/lib/rspec/rails/example/view_example_group.rb +2 -1
- data/lib/rspec/rails/fixture_file_upload_support.rb +7 -1
- data/lib/rspec/rails/fixture_support.rb +39 -17
- data/lib/rspec/rails/matchers/action_cable/have_broadcasted_to.rb +11 -4
- data/lib/rspec/rails/matchers/action_cable.rb +6 -1
- data/lib/rspec/rails/matchers/active_job.rb +69 -8
- data/lib/rspec/rails/matchers/have_enqueued_mail.rb +37 -5
- data/lib/rspec/rails/matchers/have_http_status.rb +3 -7
- data/lib/rspec/rails/matchers/send_email.rb +122 -0
- data/lib/rspec/rails/matchers.rb +1 -0
- data/lib/rspec/rails/tasks/rspec.rake +3 -1
- data/lib/rspec/rails/version.rb +1 -1
- data/lib/rspec-rails.rb +28 -3
- data.tar.gz.sig +0 -0
- metadata +39 -43
- metadata.gz.sig +0 -0
- data/lib/generators/rspec/integration/integration_generator.rb +0 -29
@@ -10,8 +10,7 @@ module RSpec
|
|
10
10
|
include ActiveRecord::TestFixtures
|
11
11
|
|
12
12
|
# @private prevent ActiveSupport::TestFixtures to start a DB transaction.
|
13
|
-
# Monkey patched to avoid collisions with 'let(:name)'
|
14
|
-
# and let(:method_name) before Rails 6.1.
|
13
|
+
# Monkey patched to avoid collisions with 'let(:name)' since Rails 6.1
|
15
14
|
def run_in_transaction?
|
16
15
|
current_example_name = (RSpec.current_example && RSpec.current_example.metadata[:description])
|
17
16
|
use_transactional_tests && !self.class.uses_transaction?(current_example_name)
|
@@ -23,10 +22,11 @@ module RSpec
|
|
23
22
|
|
24
23
|
# TestFixtures#fixture_path is deprecated and will be removed in Rails 7.2
|
25
24
|
if respond_to?(:fixture_paths=)
|
26
|
-
fixture_paths
|
25
|
+
self.fixture_paths = RSpec.configuration.fixture_paths
|
27
26
|
else
|
28
27
|
self.fixture_path = RSpec.configuration.fixture_path
|
29
28
|
end
|
29
|
+
|
30
30
|
self.use_transactional_tests = RSpec.configuration.use_transactional_fixtures
|
31
31
|
self.use_instantiated_fixtures = RSpec.configuration.use_instantiated_fixtures
|
32
32
|
|
@@ -37,28 +37,50 @@ module RSpec
|
|
37
37
|
module Fixtures
|
38
38
|
extend ActiveSupport::Concern
|
39
39
|
|
40
|
+
# rubocop:disable Metrics/BlockLength
|
40
41
|
class_methods do
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
42
|
+
if ::Rails.version.to_f >= 7.1
|
43
|
+
def fixtures(*args)
|
44
|
+
super.tap do
|
45
|
+
fixture_sets.each_pair do |method_name, fixture_name|
|
46
|
+
proxy_method_warning_if_called_in_before_context_scope(method_name, fixture_name)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def proxy_method_warning_if_called_in_before_context_scope(method_name, fixture_name)
|
52
|
+
define_method(method_name) do |*args, **kwargs, &blk|
|
53
|
+
if RSpec.current_scope == :before_context_hook
|
54
|
+
RSpec.warn_with("Calling fixture method in before :context ")
|
55
|
+
else
|
56
|
+
access_fixture(fixture_name, *args, **kwargs, &blk)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
else
|
61
|
+
def fixtures(*args)
|
62
|
+
orig_methods = private_instance_methods
|
63
|
+
super.tap do
|
64
|
+
new_methods = private_instance_methods - orig_methods
|
65
|
+
new_methods.each do |method_name|
|
66
|
+
proxy_method_warning_if_called_in_before_context_scope(method_name)
|
67
|
+
end
|
47
68
|
end
|
48
69
|
end
|
49
|
-
end
|
50
70
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
71
|
+
def proxy_method_warning_if_called_in_before_context_scope(method_name)
|
72
|
+
orig_implementation = instance_method(method_name)
|
73
|
+
define_method(method_name) do |*args, &blk|
|
74
|
+
if RSpec.current_scope == :before_context_hook
|
75
|
+
RSpec.warn_with("Calling fixture method in before :context ")
|
76
|
+
else
|
77
|
+
orig_implementation.bind(self).call(*args, &blk)
|
78
|
+
end
|
58
79
|
end
|
59
80
|
end
|
60
81
|
end
|
61
82
|
end
|
83
|
+
# rubocop:enable Metrics/BlockLength
|
62
84
|
end
|
63
85
|
end
|
64
86
|
end
|
@@ -51,6 +51,10 @@ module RSpec
|
|
51
51
|
exactly(:thrice)
|
52
52
|
end
|
53
53
|
|
54
|
+
def description
|
55
|
+
"have broadcasted #{base_description}"
|
56
|
+
end
|
57
|
+
|
54
58
|
def failure_message
|
55
59
|
"expected to broadcast #{base_message}".tap do |msg|
|
56
60
|
if @unmatching_msgs.any?
|
@@ -112,7 +116,7 @@ module RSpec
|
|
112
116
|
decoded = ActiveSupport::JSON.decode(msg)
|
113
117
|
decoded = decoded.with_indifferent_access if decoded.is_a?(Hash)
|
114
118
|
|
115
|
-
if @data.nil? || @data
|
119
|
+
if @data.nil? || values_match?(@data, decoded)
|
116
120
|
@block.call(decoded)
|
117
121
|
true
|
118
122
|
else
|
@@ -140,18 +144,21 @@ module RSpec
|
|
140
144
|
end
|
141
145
|
end
|
142
146
|
|
143
|
-
def
|
147
|
+
def base_description
|
144
148
|
"#{message_expectation_modifier} #{@expected_number} messages to #{stream}".tap do |msg|
|
145
149
|
msg << " with #{data_description(@data)}" unless @data.nil?
|
146
|
-
msg << ", but broadcast #{@matching_msgs_count}"
|
147
150
|
end
|
148
151
|
end
|
149
152
|
|
153
|
+
def base_message
|
154
|
+
"#{base_description}, but broadcast #{@matching_msgs_count}"
|
155
|
+
end
|
156
|
+
|
150
157
|
def data_description(data)
|
151
158
|
if data.is_a?(RSpec::Matchers::Composable)
|
152
159
|
data.description
|
153
160
|
else
|
154
|
-
data
|
161
|
+
data.inspect
|
155
162
|
end
|
156
163
|
end
|
157
164
|
|
@@ -3,6 +3,8 @@ require "rspec/rails/matchers/action_cable/have_broadcasted_to"
|
|
3
3
|
module RSpec
|
4
4
|
module Rails
|
5
5
|
module Matchers
|
6
|
+
extend RSpec::Matchers::DSL
|
7
|
+
|
6
8
|
# Namespace for various implementations of ActionCable features
|
7
9
|
#
|
8
10
|
# @api private
|
@@ -50,7 +52,10 @@ module RSpec
|
|
50
52
|
|
51
53
|
ActionCable::HaveBroadcastedTo.new(target, channel: described_class)
|
52
54
|
end
|
53
|
-
|
55
|
+
|
56
|
+
alias_matcher :broadcast_to, :have_broadcasted_to do |desc|
|
57
|
+
desc.gsub("have broadcasted", "broadcast")
|
58
|
+
end
|
54
59
|
|
55
60
|
private
|
56
61
|
|
@@ -14,6 +14,7 @@ module RSpec
|
|
14
14
|
def initialize
|
15
15
|
@args = []
|
16
16
|
@queue = nil
|
17
|
+
@priority = nil
|
17
18
|
@at = nil
|
18
19
|
@block = proc { }
|
19
20
|
set_expected_number(:exactly, 1)
|
@@ -30,6 +31,11 @@ module RSpec
|
|
30
31
|
self
|
31
32
|
end
|
32
33
|
|
34
|
+
def at_priority(priority)
|
35
|
+
@priority = priority.to_i
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
33
39
|
def at(time_or_date)
|
34
40
|
case time_or_date
|
35
41
|
when Time then @at = Time.at(time_or_date.to_f)
|
@@ -71,6 +77,8 @@ module RSpec
|
|
71
77
|
end
|
72
78
|
|
73
79
|
def failure_message
|
80
|
+
return @failure_message if defined?(@failure_message)
|
81
|
+
|
74
82
|
"expected to #{self.class::FAILURE_MESSAGE_EXPECTATION_ACTION} #{base_message}".tap do |msg|
|
75
83
|
if @unmatching_jobs.any?
|
76
84
|
msg << "\nQueued jobs:"
|
@@ -101,7 +109,7 @@ module RSpec
|
|
101
109
|
|
102
110
|
def check(jobs)
|
103
111
|
@matching_jobs, @unmatching_jobs = jobs.partition do |job|
|
104
|
-
if
|
112
|
+
if matches_constraints?(job)
|
105
113
|
args = deserialize_arguments(job)
|
106
114
|
@block.call(*args)
|
107
115
|
true
|
@@ -109,6 +117,12 @@ module RSpec
|
|
109
117
|
false
|
110
118
|
end
|
111
119
|
end
|
120
|
+
|
121
|
+
if (signature_mismatch = detect_args_signature_mismatch(@matching_jobs))
|
122
|
+
@failure_message = signature_mismatch
|
123
|
+
return false
|
124
|
+
end
|
125
|
+
|
112
126
|
@matching_jobs_count = @matching_jobs.size
|
113
127
|
|
114
128
|
case @expectation_type
|
@@ -123,6 +137,7 @@ module RSpec
|
|
123
137
|
msg << " with #{@args}," if @args.any?
|
124
138
|
msg << " on queue #{@queue}," if @queue
|
125
139
|
msg << " at #{@at.inspect}," if @at
|
140
|
+
msg << " with priority #{@priority}," if @priority
|
126
141
|
msg << " but #{self.class::MESSAGE_EXPECTATION_ACTION} #{@matching_jobs_count}"
|
127
142
|
end
|
128
143
|
end
|
@@ -132,13 +147,23 @@ module RSpec
|
|
132
147
|
msg_parts << "with #{deserialize_arguments(job)}" if job[:args].any?
|
133
148
|
msg_parts << "on queue #{job[:queue]}" if job[:queue]
|
134
149
|
msg_parts << "at #{Time.at(job[:at])}" if job[:at]
|
150
|
+
msg_parts <<
|
151
|
+
if job[:priority]
|
152
|
+
"with priority #{job[:priority]}"
|
153
|
+
else
|
154
|
+
"with no priority specified"
|
155
|
+
end
|
135
156
|
|
136
157
|
"#{job[:job].name} job".tap do |msg|
|
137
158
|
msg << " #{msg_parts.join(', ')}" if msg_parts.any?
|
138
159
|
end
|
139
160
|
end
|
140
161
|
|
141
|
-
def
|
162
|
+
def matches_constraints?(job)
|
163
|
+
job_matches?(job) && arguments_match?(job) && queue_match?(job) && at_match?(job) && priority_match?(job)
|
164
|
+
end
|
165
|
+
|
166
|
+
def job_matches?(job)
|
142
167
|
@job ? @job == job[:job] : true
|
143
168
|
end
|
144
169
|
|
@@ -152,12 +177,48 @@ module RSpec
|
|
152
177
|
end
|
153
178
|
end
|
154
179
|
|
180
|
+
def detect_args_signature_mismatch(jobs)
|
181
|
+
return if skip_signature_verification?
|
182
|
+
|
183
|
+
jobs.each do |job|
|
184
|
+
args = deserialize_arguments(job)
|
185
|
+
|
186
|
+
if (signature_mismatch = check_args_signature_mismatch(job.fetch(:job), :perform, args))
|
187
|
+
return signature_mismatch
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
nil
|
192
|
+
end
|
193
|
+
|
194
|
+
def skip_signature_verification?
|
195
|
+
return true unless defined?(::RSpec::Mocks) && (::RSpec::Mocks.respond_to?(:configuration))
|
196
|
+
|
197
|
+
!RSpec::Mocks.configuration.verify_partial_doubles? ||
|
198
|
+
RSpec::Mocks.configuration.temporarily_suppress_partial_double_verification
|
199
|
+
end
|
200
|
+
|
201
|
+
def check_args_signature_mismatch(job_class, job_method, args)
|
202
|
+
signature = Support::MethodSignature.new(job_class.public_instance_method(job_method))
|
203
|
+
verifier = Support::StrictSignatureVerifier.new(signature, args)
|
204
|
+
|
205
|
+
unless verifier.valid?
|
206
|
+
"Incorrect arguments passed to #{job_class.name}: #{verifier.error_message}"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
155
210
|
def queue_match?(job)
|
156
211
|
return true unless @queue
|
157
212
|
|
158
213
|
@queue == job[:queue]
|
159
214
|
end
|
160
215
|
|
216
|
+
def priority_match?(job)
|
217
|
+
return true unless @priority
|
218
|
+
|
219
|
+
@priority == job[:priority]
|
220
|
+
end
|
221
|
+
|
161
222
|
def at_match?(job)
|
162
223
|
return true unless @at
|
163
224
|
return job[:at].nil? if @at == :no_wait
|
@@ -384,33 +445,33 @@ module RSpec
|
|
384
445
|
#
|
385
446
|
# @example
|
386
447
|
# expect {
|
387
|
-
#
|
448
|
+
# perform_enqueued_jobs { HeavyLiftingJob.perform_later }
|
388
449
|
# }.to have_performed_job
|
389
450
|
#
|
390
451
|
# expect {
|
391
|
-
#
|
452
|
+
# perform_enqueued_jobs {
|
392
453
|
# HelloJob.perform_later
|
393
454
|
# HeavyLiftingJob.perform_later
|
394
455
|
# }
|
395
456
|
# }.to have_performed_job(HelloJob).exactly(:once)
|
396
457
|
#
|
397
458
|
# expect {
|
398
|
-
#
|
459
|
+
# perform_enqueued_jobs { 3.times { HelloJob.perform_later } }
|
399
460
|
# }.to have_performed_job(HelloJob).at_least(2).times
|
400
461
|
#
|
401
462
|
# expect {
|
402
|
-
#
|
463
|
+
# perform_enqueued_jobs { HelloJob.perform_later }
|
403
464
|
# }.to have_performed_job(HelloJob).at_most(:twice)
|
404
465
|
#
|
405
466
|
# expect {
|
406
|
-
#
|
467
|
+
# perform_enqueued_jobs {
|
407
468
|
# HelloJob.perform_later
|
408
469
|
# HeavyLiftingJob.perform_later
|
409
470
|
# }
|
410
471
|
# }.to have_performed_job(HelloJob).and have_performed_job(HeavyLiftingJob)
|
411
472
|
#
|
412
473
|
# expect {
|
413
|
-
#
|
474
|
+
# perform_enqueued_jobs {
|
414
475
|
# HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
|
415
476
|
# }
|
416
477
|
# }.to have_performed_job.with(42).on_queue("low").at(Date.tomorrow.noon)
|
@@ -41,6 +41,8 @@ module RSpec
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def failure_message
|
44
|
+
return @failure_message if defined?(@failure_message)
|
45
|
+
|
44
46
|
"expected to enqueue #{base_message}".tap do |msg|
|
45
47
|
msg << "\n#{unmatching_mail_jobs_message}" if unmatching_mail_jobs.any?
|
46
48
|
end
|
@@ -70,7 +72,7 @@ module RSpec
|
|
70
72
|
@mailer_class ? @mailer_class.name : 'ActionMailer::Base'
|
71
73
|
end
|
72
74
|
|
73
|
-
def
|
75
|
+
def job_matches?(job)
|
74
76
|
legacy_mail?(job) || parameterized_mail?(job) || unified_mail?(job)
|
75
77
|
end
|
76
78
|
|
@@ -89,6 +91,23 @@ module RSpec
|
|
89
91
|
super(job)
|
90
92
|
end
|
91
93
|
|
94
|
+
def detect_args_signature_mismatch(jobs)
|
95
|
+
return if @method_name.nil?
|
96
|
+
return if skip_signature_verification?
|
97
|
+
|
98
|
+
mailer_class = mailer_class_name.constantize
|
99
|
+
|
100
|
+
jobs.each do |job|
|
101
|
+
mailer_args = extract_args_without_parameterized_params(job)
|
102
|
+
|
103
|
+
if (signature_mismatch = check_args_signature_mismatch(mailer_class, @method_name, mailer_args))
|
104
|
+
return signature_mismatch
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
|
92
111
|
def base_mailer_args
|
93
112
|
[mailer_class_name, @method_name.to_s, MAILER_JOB_METHOD]
|
94
113
|
end
|
@@ -105,18 +124,18 @@ module RSpec
|
|
105
124
|
|
106
125
|
def unmatching_mail_jobs
|
107
126
|
@unmatching_jobs.select do |job|
|
108
|
-
|
127
|
+
job_matches?(job)
|
109
128
|
end
|
110
129
|
end
|
111
130
|
|
112
131
|
def unmatching_mail_jobs_message
|
113
|
-
|
132
|
+
messages = ["Queued deliveries:"]
|
114
133
|
|
115
134
|
unmatching_mail_jobs.each do |job|
|
116
|
-
|
135
|
+
messages << " #{mail_job_message(job)}"
|
117
136
|
end
|
118
137
|
|
119
|
-
|
138
|
+
messages.join("\n")
|
120
139
|
end
|
121
140
|
|
122
141
|
def mail_job_message(job)
|
@@ -157,6 +176,19 @@ module RSpec
|
|
157
176
|
end
|
158
177
|
end
|
159
178
|
|
179
|
+
def extract_args_without_parameterized_params(job)
|
180
|
+
args = deserialize_arguments(job)
|
181
|
+
mailer_args = args - base_mailer_args
|
182
|
+
|
183
|
+
if parameterized_mail?(job)
|
184
|
+
mailer_args = mailer_args[1..-1] # ignore parameterized params
|
185
|
+
elsif mailer_args.last.is_a?(Hash) && mailer_args.last.key?(:args)
|
186
|
+
mailer_args = args.last[:args]
|
187
|
+
end
|
188
|
+
|
189
|
+
mailer_args
|
190
|
+
end
|
191
|
+
|
160
192
|
def legacy_mail?(job)
|
161
193
|
RSpec::Rails::FeatureCheck.has_action_mailer_legacy_delivery_job? && job[:job] <= ActionMailer::DeliveryJob
|
162
194
|
end
|
@@ -33,7 +33,7 @@ module RSpec
|
|
33
33
|
# @param obj [Object] object to convert to a response
|
34
34
|
# @return [ActionDispatch::TestResponse]
|
35
35
|
def as_test_response(obj)
|
36
|
-
if ::ActionDispatch::Response === obj
|
36
|
+
if ::ActionDispatch::Response === obj || ::Rack::MockResponse === obj
|
37
37
|
::ActionDispatch::TestResponse.from_response(obj)
|
38
38
|
elsif ::ActionDispatch::TestResponse === obj
|
39
39
|
obj
|
@@ -216,11 +216,7 @@ module RSpec
|
|
216
216
|
# @see Rack::Utils::SYMBOL_TO_STATUS_CODE
|
217
217
|
# @raise [ArgumentError] if an associated code could not be found
|
218
218
|
def set_expected_code!
|
219
|
-
@expected ||=
|
220
|
-
Rack::Utils::SYMBOL_TO_STATUS_CODE.fetch(expected_status) do
|
221
|
-
raise ArgumentError,
|
222
|
-
"Invalid HTTP status: #{expected_status.inspect}"
|
223
|
-
end
|
219
|
+
@expected ||= Rack::Utils.status_code(expected_status)
|
224
220
|
end
|
225
221
|
end
|
226
222
|
|
@@ -237,7 +233,7 @@ module RSpec
|
|
237
233
|
# expect(response).to have_http_status(:redirect)
|
238
234
|
#
|
239
235
|
# @see RSpec::Rails::Matchers#have_http_status
|
240
|
-
# @see https://github.com/rails/rails/blob/
|
236
|
+
# @see https://github.com/rails/rails/blob/7-2-stable/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
|
241
237
|
class GenericStatus < RSpec::Rails::Matchers::BaseMatcher
|
242
238
|
include HaveHttpStatus
|
243
239
|
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Rails
|
5
|
+
module Matchers
|
6
|
+
# @api private
|
7
|
+
#
|
8
|
+
# Matcher class for `send_email`. Should not be instantiated directly.
|
9
|
+
#
|
10
|
+
# @see RSpec::Rails::Matchers#send_email
|
11
|
+
class SendEmail < RSpec::Rails::Matchers::BaseMatcher
|
12
|
+
# @api private
|
13
|
+
# Define the email attributes that should be included in the inspection output.
|
14
|
+
INSPECT_EMAIL_ATTRIBUTES = %i[subject from to cc bcc].freeze
|
15
|
+
|
16
|
+
def initialize(criteria)
|
17
|
+
@criteria = criteria
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def supports_value_expectations?
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
def supports_block_expectations?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def matches?(block)
|
31
|
+
define_matched_emails(block)
|
32
|
+
|
33
|
+
@matched_emails.one?
|
34
|
+
end
|
35
|
+
|
36
|
+
# @api private
|
37
|
+
# @return [String]
|
38
|
+
def failure_message
|
39
|
+
result =
|
40
|
+
if multiple_match?
|
41
|
+
"More than 1 matching emails were sent."
|
42
|
+
else
|
43
|
+
"No matching emails were sent."
|
44
|
+
end
|
45
|
+
"#{result}#{sent_emails_message}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# @api private
|
49
|
+
# @return [String]
|
50
|
+
def failure_message_when_negated
|
51
|
+
"Expected not to send an email but it was sent."
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def diffable?
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def deliveries
|
61
|
+
ActionMailer::Base.deliveries
|
62
|
+
end
|
63
|
+
|
64
|
+
def define_matched_emails(block)
|
65
|
+
before = deliveries.dup
|
66
|
+
|
67
|
+
block.call
|
68
|
+
|
69
|
+
after = deliveries
|
70
|
+
|
71
|
+
@diff = after - before
|
72
|
+
@matched_emails = @diff.select(&method(:matched_email?))
|
73
|
+
end
|
74
|
+
|
75
|
+
def matched_email?(email)
|
76
|
+
@criteria.all? do |attr, value|
|
77
|
+
expected =
|
78
|
+
case attr
|
79
|
+
when :to, :from, :cc, :bcc then Array(value)
|
80
|
+
else
|
81
|
+
value
|
82
|
+
end
|
83
|
+
|
84
|
+
values_match?(expected, email.public_send(attr))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def multiple_match?
|
89
|
+
@matched_emails.many?
|
90
|
+
end
|
91
|
+
|
92
|
+
def sent_emails_message
|
93
|
+
if @diff.empty?
|
94
|
+
"\n\nThere were no any emails sent inside the expectation block."
|
95
|
+
else
|
96
|
+
sent_emails =
|
97
|
+
@diff.map do |email|
|
98
|
+
inspected = INSPECT_EMAIL_ATTRIBUTES.map { |attr| "#{attr}: #{email.public_send(attr)}" }.join(", ")
|
99
|
+
"- #{inspected}"
|
100
|
+
end.join("\n")
|
101
|
+
"\n\nThe following emails were sent:\n#{sent_emails}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# @api public
|
107
|
+
# Check email sending with specific parameters.
|
108
|
+
#
|
109
|
+
# @example Positive expectation
|
110
|
+
# expect { action }.to send_email
|
111
|
+
#
|
112
|
+
# @example Negative expectations
|
113
|
+
# expect { action }.not_to send_email
|
114
|
+
#
|
115
|
+
# @example More precise expectation with attributes to match
|
116
|
+
# expect { action }.to send_email(to: 'test@example.com', subject: 'Confirm email')
|
117
|
+
def send_email(criteria = {})
|
118
|
+
SendEmail.new(criteria)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/rspec/rails/matchers.rb
CHANGED
@@ -20,6 +20,7 @@ require 'rspec/rails/matchers/be_a_new'
|
|
20
20
|
require 'rspec/rails/matchers/relation_match_array'
|
21
21
|
require 'rspec/rails/matchers/be_valid'
|
22
22
|
require 'rspec/rails/matchers/have_http_status'
|
23
|
+
require 'rspec/rails/matchers/send_email'
|
23
24
|
|
24
25
|
if RSpec::Rails::FeatureCheck.has_active_job?
|
25
26
|
require 'rspec/rails/matchers/active_job'
|
data/lib/rspec/rails/version.rb
CHANGED
data/lib/rspec-rails.rb
CHANGED
@@ -9,6 +9,23 @@ module RSpec
|
|
9
9
|
# As of Rails 5.1.0 you can register directories to work with `rake notes`
|
10
10
|
require 'rails/source_annotation_extractor'
|
11
11
|
::Rails::SourceAnnotationExtractor::Annotation.register_directories("spec")
|
12
|
+
|
13
|
+
# As of Rails 8.0.0 you can register directories to work with `rails stats`
|
14
|
+
if ::Rails::VERSION::STRING >= "8.0.0"
|
15
|
+
require 'rails/code_statistics'
|
16
|
+
|
17
|
+
dirs = Dir['./spec/**/*_spec.rb']
|
18
|
+
.map { |f| f.sub(/^\.\/(spec\/\w+)\/.*/, '\\1') }
|
19
|
+
.uniq
|
20
|
+
.select { |f| File.directory?(f) }
|
21
|
+
|
22
|
+
Hash[dirs.map { |d| [d.split('/').last, d] }].each do |type, dir|
|
23
|
+
name = type.singularize.capitalize
|
24
|
+
|
25
|
+
::Rails::CodeStatistics.register_directory "#{name} specs", dir, test_directory: true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
12
29
|
generators = config.app_generators
|
13
30
|
generators.integration_tool :rspec
|
14
31
|
generators.test_framework :rspec
|
@@ -47,10 +64,18 @@ module RSpec
|
|
47
64
|
end
|
48
65
|
end
|
49
66
|
|
50
|
-
|
51
|
-
|
67
|
+
if ::Rails::VERSION::STRING >= "7.1.0"
|
68
|
+
def config_default_preview_path(options)
|
69
|
+
return unless options.preview_paths.empty?
|
52
70
|
|
53
|
-
|
71
|
+
options.preview_paths << "#{::Rails.root}/spec/mailers/previews"
|
72
|
+
end
|
73
|
+
else
|
74
|
+
def config_default_preview_path(options)
|
75
|
+
return unless options.preview_path.blank?
|
76
|
+
|
77
|
+
options.preview_path = "#{::Rails.root}/spec/mailers/previews"
|
78
|
+
end
|
54
79
|
end
|
55
80
|
|
56
81
|
def supports_action_mailer_previews?(config)
|
data.tar.gz.sig
CHANGED
Binary file
|