mini-quick-sys 0.0.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.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/mini-quick-sys.gemspec +12 -0
  3. data/rspec-rails-8.0.4/Capybara.md +28 -0
  4. data/rspec-rails-8.0.4/Changelog.md +1408 -0
  5. data/rspec-rails-8.0.4/LICENSE.md +25 -0
  6. data/rspec-rails-8.0.4/README.md +386 -0
  7. data/rspec-rails-8.0.4/lib/generators/rspec/authentication/authentication_generator.rb +25 -0
  8. data/rspec-rails-8.0.4/lib/generators/rspec/authentication/templates/user_spec.rb +5 -0
  9. data/rspec-rails-8.0.4/lib/generators/rspec/authentication/templates/users.yml +11 -0
  10. data/rspec-rails-8.0.4/lib/generators/rspec/channel/channel_generator.rb +12 -0
  11. data/rspec-rails-8.0.4/lib/generators/rspec/channel/templates/channel_spec.rb.erb +7 -0
  12. data/rspec-rails-8.0.4/lib/generators/rspec/controller/controller_generator.rb +51 -0
  13. data/rspec-rails-8.0.4/lib/generators/rspec/controller/templates/controller_spec.rb +16 -0
  14. data/rspec-rails-8.0.4/lib/generators/rspec/controller/templates/request_spec.rb +19 -0
  15. data/rspec-rails-8.0.4/lib/generators/rspec/controller/templates/routing_spec.rb +13 -0
  16. data/rspec-rails-8.0.4/lib/generators/rspec/controller/templates/view_spec.rb +5 -0
  17. data/rspec-rails-8.0.4/lib/generators/rspec/feature/feature_generator.rb +29 -0
  18. data/rspec-rails-8.0.4/lib/generators/rspec/feature/templates/feature_singular_spec.rb +5 -0
  19. data/rspec-rails-8.0.4/lib/generators/rspec/feature/templates/feature_spec.rb +5 -0
  20. data/rspec-rails-8.0.4/lib/generators/rspec/generator/generator_generator.rb +24 -0
  21. data/rspec-rails-8.0.4/lib/generators/rspec/generator/templates/generator_spec.rb +5 -0
  22. data/rspec-rails-8.0.4/lib/generators/rspec/helper/helper_generator.rb +16 -0
  23. data/rspec-rails-8.0.4/lib/generators/rspec/helper/templates/helper_spec.rb +17 -0
  24. data/rspec-rails-8.0.4/lib/generators/rspec/install/install_generator.rb +80 -0
  25. data/rspec-rails-8.0.4/lib/generators/rspec/install/templates/spec/rails_helper.rb +91 -0
  26. data/rspec-rails-8.0.4/lib/generators/rspec/job/job_generator.rb +13 -0
  27. data/rspec-rails-8.0.4/lib/generators/rspec/job/templates/job_spec.rb.erb +7 -0
  28. data/rspec-rails-8.0.4/lib/generators/rspec/mailbox/mailbox_generator.rb +14 -0
  29. data/rspec-rails-8.0.4/lib/generators/rspec/mailbox/templates/mailbox_spec.rb.erb +7 -0
  30. data/rspec-rails-8.0.4/lib/generators/rspec/mailer/mailer_generator.rb +30 -0
  31. data/rspec-rails-8.0.4/lib/generators/rspec/mailer/templates/fixture +3 -0
  32. data/rspec-rails-8.0.4/lib/generators/rspec/mailer/templates/mailer_spec.rb +25 -0
  33. data/rspec-rails-8.0.4/lib/generators/rspec/mailer/templates/preview.rb +13 -0
  34. data/rspec-rails-8.0.4/lib/generators/rspec/model/model_generator.rb +37 -0
  35. data/rspec-rails-8.0.4/lib/generators/rspec/model/templates/fixtures.yml +19 -0
  36. data/rspec-rails-8.0.4/lib/generators/rspec/model/templates/model_spec.rb +7 -0
  37. data/rspec-rails-8.0.4/lib/generators/rspec/request/request_generator.rb +17 -0
  38. data/rspec-rails-8.0.4/lib/generators/rspec/request/templates/request_spec.rb +10 -0
  39. data/rspec-rails-8.0.4/lib/generators/rspec/scaffold/scaffold_generator.rb +136 -0
  40. data/rspec-rails-8.0.4/lib/generators/rspec/scaffold/templates/api_controller_spec.rb +129 -0
  41. data/rspec-rails-8.0.4/lib/generators/rspec/scaffold/templates/api_request_spec.rb +131 -0
  42. data/rspec-rails-8.0.4/lib/generators/rspec/scaffold/templates/controller_spec.rb +145 -0
  43. data/rspec-rails-8.0.4/lib/generators/rspec/scaffold/templates/edit_spec.rb +27 -0
  44. data/rspec-rails-8.0.4/lib/generators/rspec/scaffold/templates/index_spec.rb +26 -0
  45. data/rspec-rails-8.0.4/lib/generators/rspec/scaffold/templates/new_spec.rb +22 -0
  46. data/rspec-rails-8.0.4/lib/generators/rspec/scaffold/templates/request_spec.rb +138 -0
  47. data/rspec-rails-8.0.4/lib/generators/rspec/scaffold/templates/routing_spec.rb +46 -0
  48. data/rspec-rails-8.0.4/lib/generators/rspec/scaffold/templates/show_spec.rb +21 -0
  49. data/rspec-rails-8.0.4/lib/generators/rspec/system/system_generator.rb +24 -0
  50. data/rspec-rails-8.0.4/lib/generators/rspec/system/templates/system_spec.rb +9 -0
  51. data/rspec-rails-8.0.4/lib/generators/rspec/view/templates/view_spec.rb +5 -0
  52. data/rspec-rails-8.0.4/lib/generators/rspec/view/view_generator.rb +22 -0
  53. data/rspec-rails-8.0.4/lib/generators/rspec.rb +56 -0
  54. data/rspec-rails-8.0.4/lib/rspec/rails/active_record.rb +25 -0
  55. data/rspec-rails-8.0.4/lib/rspec/rails/adapters.rb +196 -0
  56. data/rspec-rails-8.0.4/lib/rspec/rails/configuration.rb +158 -0
  57. data/rspec-rails-8.0.4/lib/rspec/rails/example/channel_example_group.rb +93 -0
  58. data/rspec-rails-8.0.4/lib/rspec/rails/example/controller_example_group.rb +217 -0
  59. data/rspec-rails-8.0.4/lib/rspec/rails/example/feature_example_group.rb +53 -0
  60. data/rspec-rails-8.0.4/lib/rspec/rails/example/helper_example_group.rb +42 -0
  61. data/rspec-rails-8.0.4/lib/rspec/rails/example/job_example_group.rb +23 -0
  62. data/rspec-rails-8.0.4/lib/rspec/rails/example/mailbox_example_group.rb +80 -0
  63. data/rspec-rails-8.0.4/lib/rspec/rails/example/mailer_example_group.rb +38 -0
  64. data/rspec-rails-8.0.4/lib/rspec/rails/example/model_example_group.rb +11 -0
  65. data/rspec-rails-8.0.4/lib/rspec/rails/example/rails_example_group.rb +23 -0
  66. data/rspec-rails-8.0.4/lib/rspec/rails/example/request_example_group.rb +27 -0
  67. data/rspec-rails-8.0.4/lib/rspec/rails/example/routing_example_group.rb +61 -0
  68. data/rspec-rails-8.0.4/lib/rspec/rails/example/system_example_group.rb +177 -0
  69. data/rspec-rails-8.0.4/lib/rspec/rails/example/view_example_group.rb +214 -0
  70. data/rspec-rails-8.0.4/lib/rspec/rails/example.rb +13 -0
  71. data/rspec-rails-8.0.4/lib/rspec/rails/extensions/active_record/proxy.rb +11 -0
  72. data/rspec-rails-8.0.4/lib/rspec/rails/extensions.rb +1 -0
  73. data/rspec-rails-8.0.4/lib/rspec/rails/feature_check.rb +51 -0
  74. data/rspec-rails-8.0.4/lib/rspec/rails/file_fixture_support.rb +18 -0
  75. data/rspec-rails-8.0.4/lib/rspec/rails/fixture_file_upload_support.rb +39 -0
  76. data/rspec-rails-8.0.4/lib/rspec/rails/fixture_support.rb +58 -0
  77. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/action_cable/have_broadcasted_to.rb +180 -0
  78. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/action_cable/have_streams.rb +58 -0
  79. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/action_cable.rb +70 -0
  80. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/action_mailbox.rb +73 -0
  81. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/active_job.rb +534 -0
  82. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/base_matcher.rb +179 -0
  83. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/be_a_new.rb +83 -0
  84. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/be_new_record.rb +30 -0
  85. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/be_valid.rb +49 -0
  86. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/have_enqueued_mail.rb +259 -0
  87. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/have_http_status.rb +381 -0
  88. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/have_rendered.rb +64 -0
  89. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/redirect_to.rb +38 -0
  90. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/relation_match_array.rb +3 -0
  91. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/routing_matchers.rb +125 -0
  92. data/rspec-rails-8.0.4/lib/rspec/rails/matchers/send_email.rb +122 -0
  93. data/rspec-rails-8.0.4/lib/rspec/rails/matchers.rb +36 -0
  94. data/rspec-rails-8.0.4/lib/rspec/rails/tasks/rspec.rake +49 -0
  95. data/rspec-rails-8.0.4/lib/rspec/rails/vendor/capybara.rb +32 -0
  96. data/rspec-rails-8.0.4/lib/rspec/rails/version.rb +9 -0
  97. data/rspec-rails-8.0.4/lib/rspec/rails/view_assigns.rb +27 -0
  98. data/rspec-rails-8.0.4/lib/rspec/rails/view_path_builder.rb +29 -0
  99. data/rspec-rails-8.0.4/lib/rspec/rails/view_rendering.rb +166 -0
  100. data/rspec-rails-8.0.4/lib/rspec/rails/view_spec_methods.rb +56 -0
  101. data/rspec-rails-8.0.4/lib/rspec/rails.rb +18 -0
  102. data/rspec-rails-8.0.4/lib/rspec-rails.rb +91 -0
  103. metadata +142 -0
@@ -0,0 +1,534 @@
1
+ require "active_job/base"
2
+ require "active_job/arguments"
3
+
4
+ module RSpec
5
+ module Rails
6
+ module Matchers
7
+ # Namespace for various implementations of ActiveJob features
8
+ #
9
+ # @api private
10
+ module ActiveJob
11
+ # rubocop: disable Metrics/ClassLength
12
+ # @private
13
+ class Base < RSpec::Rails::Matchers::BaseMatcher
14
+ def initialize
15
+ @args = []
16
+ @queue = nil
17
+ @priority = nil
18
+ @at = nil
19
+ @block = proc { }
20
+ set_expected_number(:exactly, 1)
21
+ end
22
+
23
+ def with(*args, &block)
24
+ @args = args
25
+ @block = block if block.present?
26
+ self
27
+ end
28
+
29
+ def on_queue(queue)
30
+ @queue = queue.to_s
31
+ self
32
+ end
33
+
34
+ def at_priority(priority)
35
+ @priority = priority.to_i
36
+ self
37
+ end
38
+
39
+ def at(time_or_date)
40
+ case time_or_date
41
+ when Time then @at = Time.at(time_or_date.to_f)
42
+ else
43
+ @at = time_or_date
44
+ end
45
+ self
46
+ end
47
+
48
+ def exactly(count)
49
+ set_expected_number(:exactly, count)
50
+ self
51
+ end
52
+
53
+ def at_least(count)
54
+ set_expected_number(:at_least, count)
55
+ self
56
+ end
57
+
58
+ def at_most(count)
59
+ set_expected_number(:at_most, count)
60
+ self
61
+ end
62
+
63
+ def times
64
+ self
65
+ end
66
+
67
+ def once
68
+ exactly(:once)
69
+ end
70
+
71
+ def twice
72
+ exactly(:twice)
73
+ end
74
+
75
+ def thrice
76
+ exactly(:thrice)
77
+ end
78
+
79
+ def failure_message
80
+ return @failure_message if defined?(@failure_message)
81
+
82
+ "expected to #{self.class::FAILURE_MESSAGE_EXPECTATION_ACTION} #{base_message}".tap do |msg|
83
+ if @unmatching_jobs.any?
84
+ msg << "\nQueued jobs:"
85
+ @unmatching_jobs.each do |job|
86
+ msg << "\n #{base_job_message(job)}"
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ def failure_message_when_negated
93
+ "expected not to #{self.class::FAILURE_MESSAGE_EXPECTATION_ACTION} #{base_message}"
94
+ end
95
+
96
+ def message_expectation_modifier
97
+ case @expectation_type
98
+ when :exactly then "exactly"
99
+ when :at_most then "at most"
100
+ when :at_least then "at least"
101
+ end
102
+ end
103
+
104
+ def supports_block_expectations?
105
+ true
106
+ end
107
+
108
+ private
109
+
110
+ def check(jobs)
111
+ @matching_jobs, @unmatching_jobs = jobs.partition do |job|
112
+ if matches_constraints?(job)
113
+ args = deserialize_arguments(job)
114
+ @block.call(*args)
115
+ true
116
+ else
117
+ false
118
+ end
119
+ end
120
+
121
+ if (signature_mismatch = detect_args_signature_mismatch(@matching_jobs))
122
+ @failure_message = signature_mismatch
123
+ return false
124
+ end
125
+
126
+ @matching_jobs_count = @matching_jobs.size
127
+
128
+ case @expectation_type
129
+ when :exactly then @expected_number == @matching_jobs_count
130
+ when :at_most then @expected_number >= @matching_jobs_count
131
+ when :at_least then @expected_number <= @matching_jobs_count
132
+ end
133
+ end
134
+
135
+ def base_message
136
+ "#{message_expectation_modifier} #{@expected_number} jobs,".tap do |msg|
137
+ msg << " with #{@args}," if @args.any?
138
+ msg << " on queue #{@queue}," if @queue
139
+ msg << " at #{@at.inspect}," if @at
140
+ msg << " with priority #{@priority}," if @priority
141
+ msg << " but #{self.class::MESSAGE_EXPECTATION_ACTION} #{@matching_jobs_count}"
142
+ end
143
+ end
144
+
145
+ def base_job_message(job)
146
+ msg_parts = []
147
+ msg_parts << "with #{deserialize_arguments(job)}" if job[:args].any?
148
+ msg_parts << "on queue #{job[:queue]}" if job[:queue]
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
156
+
157
+ "#{job[:job].name} job".tap do |msg|
158
+ msg << " #{msg_parts.join(', ')}" if msg_parts.any?
159
+ end
160
+ end
161
+
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)
167
+ @job ? @job == job[:job] : true
168
+ end
169
+
170
+ def arguments_match?(job)
171
+ if @args.any?
172
+ args = serialize_and_deserialize_arguments(@args)
173
+ deserialized_args = deserialize_arguments(job)
174
+ RSpec::Mocks::ArgumentListMatcher.new(*args).args_match?(*deserialized_args)
175
+ else
176
+ true
177
+ end
178
+ end
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
+
210
+ def queue_match?(job)
211
+ return true unless @queue
212
+
213
+ @queue == job[:queue]
214
+ end
215
+
216
+ def priority_match?(job)
217
+ return true unless @priority
218
+
219
+ @priority == job[:priority]
220
+ end
221
+
222
+ def at_match?(job)
223
+ return true unless @at
224
+ return job[:at].nil? if @at == :no_wait
225
+ return false unless job[:at]
226
+
227
+ scheduled_at = Time.at(job[:at])
228
+ values_match?(@at, scheduled_at) || check_for_inprecise_value(scheduled_at)
229
+ end
230
+
231
+ def check_for_inprecise_value(scheduled_at)
232
+ return unless Time === @at && values_match?(@at.change(usec: 0), scheduled_at)
233
+
234
+ RSpec.warn_with((<<-WARNING).gsub(/^\s+\|/, '').chomp)
235
+ |[WARNING] Your expected `at(...)` value does not match the job scheduled_at value
236
+ |unless microseconds are removed. This precision error often occurs when checking
237
+ |values against `Time.current` / `Time.now` which have usec precision, but Rails
238
+ |uses `n.seconds.from_now` internally which has a usec count of `0`.
239
+ |
240
+ |Use `change(usec: 0)` to correct these values. For example:
241
+ |
242
+ |`Time.current.change(usec: 0)`
243
+ |
244
+ |Note: RSpec cannot do this for you because jobs can be scheduled with usec
245
+ |precision and we do not know whether it is on purpose or not.
246
+ |
247
+ |
248
+ WARNING
249
+ false
250
+ end
251
+
252
+ def set_expected_number(relativity, count)
253
+ @expectation_type = relativity
254
+ @expected_number = case count
255
+ when :once then 1
256
+ when :twice then 2
257
+ when :thrice then 3
258
+ else Integer(count)
259
+ end
260
+ end
261
+
262
+ def serialize_and_deserialize_arguments(args)
263
+ serialized = ::ActiveJob::Arguments.serialize(args)
264
+ ::ActiveJob::Arguments.deserialize(serialized)
265
+ rescue ::ActiveJob::SerializationError
266
+ args
267
+ end
268
+
269
+ def deserialize_arguments(job)
270
+ ::ActiveJob::Arguments.deserialize(job[:args])
271
+ rescue ::ActiveJob::DeserializationError
272
+ job[:args]
273
+ end
274
+
275
+ def queue_adapter
276
+ ::ActiveJob::Base.queue_adapter
277
+ end
278
+ end
279
+ # rubocop: enable Metrics/ClassLength
280
+
281
+ # @private
282
+ class HaveEnqueuedJob < Base
283
+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'enqueue'.freeze
284
+ MESSAGE_EXPECTATION_ACTION = 'enqueued'.freeze
285
+
286
+ def initialize(job)
287
+ super()
288
+ @job = job
289
+ end
290
+
291
+ def matches?(proc)
292
+ raise ArgumentError, "have_enqueued_job and enqueue_job only support block expectations" unless Proc === proc
293
+
294
+ original_enqueued_jobs = Set.new(queue_adapter.enqueued_jobs)
295
+ proc.call
296
+ enqueued_jobs = Set.new(queue_adapter.enqueued_jobs)
297
+
298
+ check(enqueued_jobs - original_enqueued_jobs)
299
+ end
300
+
301
+ def does_not_match?(proc)
302
+ set_expected_number(:at_least, 1)
303
+
304
+ !matches?(proc)
305
+ end
306
+ end
307
+
308
+ # @private
309
+ class HaveBeenEnqueued < Base
310
+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'enqueue'.freeze
311
+ MESSAGE_EXPECTATION_ACTION = 'enqueued'.freeze
312
+
313
+ def matches?(job)
314
+ @job = job
315
+ check(queue_adapter.enqueued_jobs)
316
+ end
317
+
318
+ def does_not_match?(proc)
319
+ set_expected_number(:at_least, 1)
320
+
321
+ !matches?(proc)
322
+ end
323
+
324
+ def supports_block_expectations?
325
+ false
326
+ end
327
+ end
328
+
329
+ # @private
330
+ class HavePerformedJob < Base
331
+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'perform'.freeze
332
+ MESSAGE_EXPECTATION_ACTION = 'performed'.freeze
333
+
334
+ def initialize(job)
335
+ super()
336
+ @job = job
337
+ end
338
+
339
+ def matches?(proc)
340
+ raise ArgumentError, "have_performed_job only supports block expectations" unless Proc === proc
341
+
342
+ original_performed_jobs_count = queue_adapter.performed_jobs.count
343
+ proc.call
344
+ in_block_jobs = queue_adapter.performed_jobs.drop(original_performed_jobs_count)
345
+
346
+ check(in_block_jobs)
347
+ end
348
+ end
349
+
350
+ # @private
351
+ class HaveBeenPerformed < Base
352
+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'perform'.freeze
353
+ MESSAGE_EXPECTATION_ACTION = 'performed'.freeze
354
+
355
+ def matches?(job)
356
+ @job = job
357
+ check(queue_adapter.performed_jobs)
358
+ end
359
+
360
+ def supports_block_expectations?
361
+ false
362
+ end
363
+ end
364
+ end
365
+
366
+ # @api public
367
+ # Passes if a job has been enqueued inside block. May chain at_least, at_most or exactly to specify a number of times.
368
+ #
369
+ # @example
370
+ # expect {
371
+ # HeavyLiftingJob.perform_later
372
+ # }.to have_enqueued_job
373
+ #
374
+ # # Using alias
375
+ # expect {
376
+ # HeavyLiftingJob.perform_later
377
+ # }.to enqueue_job
378
+ #
379
+ # expect {
380
+ # HelloJob.perform_later
381
+ # HeavyLiftingJob.perform_later
382
+ # }.to have_enqueued_job(HelloJob).exactly(:once)
383
+ #
384
+ # expect {
385
+ # 3.times { HelloJob.perform_later }
386
+ # }.to have_enqueued_job(HelloJob).at_least(2).times
387
+ #
388
+ # expect {
389
+ # HelloJob.perform_later
390
+ # }.to have_enqueued_job(HelloJob).at_most(:twice)
391
+ #
392
+ # expect {
393
+ # HelloJob.perform_later
394
+ # HeavyLiftingJob.perform_later
395
+ # }.to have_enqueued_job(HelloJob).and have_enqueued_job(HeavyLiftingJob)
396
+ #
397
+ # expect {
398
+ # HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
399
+ # }.to have_enqueued_job.with(42).on_queue("low").at(Date.tomorrow.noon)
400
+ #
401
+ # expect {
402
+ # HelloJob.set(queue: "low").perform_later(42)
403
+ # }.to have_enqueued_job.with(42).on_queue("low").at(:no_wait)
404
+ #
405
+ # expect {
406
+ # HelloJob.perform_later('rspec_rails', 'rails', 42)
407
+ # }.to have_enqueued_job.with { |from, to, times|
408
+ # # Perform more complex argument matching using dynamic arguments
409
+ # expect(from).to include "_#{to}"
410
+ # }
411
+ def have_enqueued_job(job = nil)
412
+ check_active_job_adapter
413
+ ActiveJob::HaveEnqueuedJob.new(job)
414
+ end
415
+ alias_method :enqueue_job, :have_enqueued_job
416
+
417
+ # @api public
418
+ # Passes if a job has been enqueued. May chain at_least, at_most or exactly to specify a number of times.
419
+ #
420
+ # @example
421
+ # before { ActiveJob::Base.queue_adapter.enqueued_jobs.clear }
422
+ #
423
+ # HeavyLiftingJob.perform_later
424
+ # expect(HeavyLiftingJob).to have_been_enqueued
425
+ #
426
+ # HelloJob.perform_later
427
+ # HeavyLiftingJob.perform_later
428
+ # expect(HeavyLiftingJob).to have_been_enqueued.exactly(:once)
429
+ #
430
+ # 3.times { HelloJob.perform_later }
431
+ # expect(HelloJob).to have_been_enqueued.at_least(2).times
432
+ #
433
+ # HelloJob.perform_later
434
+ # expect(HelloJob).to enqueue_job(HelloJob).at_most(:twice)
435
+ #
436
+ # HelloJob.perform_later
437
+ # HeavyLiftingJob.perform_later
438
+ # expect(HelloJob).to have_been_enqueued
439
+ # expect(HeavyLiftingJob).to have_been_enqueued
440
+ #
441
+ # HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
442
+ # expect(HelloJob).to have_been_enqueued.with(42).on_queue("low").at(Date.tomorrow.noon)
443
+ #
444
+ # HelloJob.set(queue: "low").perform_later(42)
445
+ # expect(HelloJob).to have_been_enqueued.with(42).on_queue("low").at(:no_wait)
446
+ def have_been_enqueued
447
+ check_active_job_adapter
448
+ ActiveJob::HaveBeenEnqueued.new
449
+ end
450
+
451
+ # @api public
452
+ # Passes if a job has been performed inside block. May chain at_least, at_most or exactly to specify a number of times.
453
+ #
454
+ # @example
455
+ # expect {
456
+ # perform_enqueued_jobs { HeavyLiftingJob.perform_later }
457
+ # }.to have_performed_job
458
+ #
459
+ # expect {
460
+ # perform_enqueued_jobs {
461
+ # HelloJob.perform_later
462
+ # HeavyLiftingJob.perform_later
463
+ # }
464
+ # }.to have_performed_job(HelloJob).exactly(:once)
465
+ #
466
+ # expect {
467
+ # perform_enqueued_jobs { 3.times { HelloJob.perform_later } }
468
+ # }.to have_performed_job(HelloJob).at_least(2).times
469
+ #
470
+ # expect {
471
+ # perform_enqueued_jobs { HelloJob.perform_later }
472
+ # }.to have_performed_job(HelloJob).at_most(:twice)
473
+ #
474
+ # expect {
475
+ # perform_enqueued_jobs {
476
+ # HelloJob.perform_later
477
+ # HeavyLiftingJob.perform_later
478
+ # }
479
+ # }.to have_performed_job(HelloJob).and have_performed_job(HeavyLiftingJob)
480
+ #
481
+ # expect {
482
+ # perform_enqueued_jobs {
483
+ # HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
484
+ # }
485
+ # }.to have_performed_job.with(42).on_queue("low").at(Date.tomorrow.noon)
486
+ def have_performed_job(job = nil)
487
+ check_active_job_adapter
488
+ ActiveJob::HavePerformedJob.new(job)
489
+ end
490
+ alias_method :perform_job, :have_performed_job
491
+
492
+ # @api public
493
+ # Passes if a job has been performed. May chain at_least, at_most or exactly to specify a number of times.
494
+ #
495
+ # @example
496
+ # before do
497
+ # ActiveJob::Base.queue_adapter.performed_jobs.clear
498
+ # ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
499
+ # ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
500
+ # end
501
+ #
502
+ # HeavyLiftingJob.perform_later
503
+ # expect(HeavyLiftingJob).to have_been_performed
504
+ #
505
+ # HelloJob.perform_later
506
+ # HeavyLiftingJob.perform_later
507
+ # expect(HeavyLiftingJob).to have_been_performed.exactly(:once)
508
+ #
509
+ # 3.times { HelloJob.perform_later }
510
+ # expect(HelloJob).to have_been_performed.at_least(2).times
511
+ #
512
+ # HelloJob.perform_later
513
+ # HeavyLiftingJob.perform_later
514
+ # expect(HelloJob).to have_been_performed
515
+ # expect(HeavyLiftingJob).to have_been_performed
516
+ #
517
+ # HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
518
+ # expect(HelloJob).to have_been_performed.with(42).on_queue("low").at(Date.tomorrow.noon)
519
+ def have_been_performed
520
+ check_active_job_adapter
521
+ ActiveJob::HaveBeenPerformed.new
522
+ end
523
+
524
+ private
525
+
526
+ # @private
527
+ def check_active_job_adapter
528
+ return if ::ActiveJob::QueueAdapters::TestAdapter === ::ActiveJob::Base.queue_adapter
529
+
530
+ raise StandardError, "To use ActiveJob matchers set `ActiveJob::Base.queue_adapter = :test`"
531
+ end
532
+ end
533
+ end
534
+ end
@@ -0,0 +1,179 @@
1
+ module RSpec
2
+ module Rails
3
+ module Matchers
4
+ # @api private
5
+ #
6
+ # Base class to build matchers. Should not be instantiated directly.
7
+ class BaseMatcher
8
+ include RSpec::Matchers::Composable
9
+
10
+ # @api private
11
+ # Used to detect when no arg is passed to `initialize`.
12
+ # `nil` cannot be used because it's a valid value to pass.
13
+ UNDEFINED = Object.new.freeze
14
+
15
+ # @private
16
+ attr_reader :actual, :expected, :rescued_exception
17
+
18
+ # @private
19
+ attr_writer :matcher_name
20
+
21
+ def initialize(expected = UNDEFINED)
22
+ @expected = expected unless UNDEFINED.equal?(expected)
23
+ end
24
+
25
+ # @api private
26
+ # Indicates if the match is successful. Delegates to `match`, which
27
+ # should be defined on a subclass. Takes care of consistently
28
+ # initializing the `actual` attribute.
29
+ def matches?(actual)
30
+ @actual = actual
31
+ match(expected, actual)
32
+ end
33
+
34
+ # @api private
35
+ # Used to wrap a block of code that will indicate failure by
36
+ # raising one of the named exceptions.
37
+ #
38
+ # This is used by rspec-rails for some of its matchers that
39
+ # wrap rails' assertions.
40
+ def match_unless_raises(*exceptions)
41
+ exceptions.unshift Exception if exceptions.empty?
42
+ begin
43
+ yield
44
+ true
45
+ rescue *exceptions => @rescued_exception
46
+ false
47
+ end
48
+ end
49
+
50
+ # @api private
51
+ # Generates a description using {RSpec::Matchers::EnglishPhrasing}.
52
+ # @return [String]
53
+ def description
54
+ desc = RSpec::Matchers::EnglishPhrasing.split_words(self.class.matcher_name)
55
+ desc << RSpec::Matchers::EnglishPhrasing.list(@expected) if defined?(@expected)
56
+ desc
57
+ end
58
+
59
+ # @api private
60
+ # Matchers are not diffable by default. Override this to make your
61
+ # subclass diffable.
62
+ def diffable?
63
+ false
64
+ end
65
+
66
+ # @api private
67
+ # Most matchers are value matchers (i.e. meant to work with `expect(value)`)
68
+ # rather than block matchers (i.e. meant to work with `expect { }`), so
69
+ # this defaults to false. Block matchers must override this to return true.
70
+ def supports_block_expectations?
71
+ false
72
+ end
73
+
74
+ # @api private
75
+ def expects_call_stack_jump?
76
+ false
77
+ end
78
+
79
+ # @private
80
+ def expected_formatted
81
+ RSpec::Support::ObjectFormatter.format(@expected)
82
+ end
83
+
84
+ # @private
85
+ def actual_formatted
86
+ RSpec::Support::ObjectFormatter.format(@actual)
87
+ end
88
+
89
+ # @private
90
+ def self.matcher_name
91
+ @matcher_name ||= underscore(name.split('::').last)
92
+ end
93
+
94
+ # @private
95
+ def matcher_name
96
+ if defined?(@matcher_name)
97
+ @matcher_name
98
+ else
99
+ self.class.matcher_name
100
+ end
101
+ end
102
+
103
+ # @private
104
+ # Borrowed from ActiveSupport.
105
+ def self.underscore(camel_cased_word)
106
+ word = camel_cased_word.to_s.dup
107
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
108
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
109
+ word.tr!('-', '_')
110
+ word.downcase!
111
+ word
112
+ end
113
+ private_class_method :underscore
114
+
115
+ private
116
+
117
+ def assert_ivars(*expected_ivars)
118
+ return unless (expected_ivars - present_ivars).any?
119
+
120
+ ivar_list = RSpec::Matchers::EnglishPhrasing.list(expected_ivars)
121
+ raise "#{self.class.name} needs to supply#{ivar_list}"
122
+ end
123
+
124
+ alias present_ivars instance_variables
125
+
126
+ # @private
127
+ module HashFormatting
128
+ # `{ :a => 5, :b => 2 }.inspect` produces:
129
+ #
130
+ # {:a=>5, :b=>2}
131
+ #
132
+ # ...but it looks much better as:
133
+ #
134
+ # {:a => 5, :b => 2}
135
+ #
136
+ # This is idempotent and safe to run on a string multiple times.
137
+ def improve_hash_formatting(inspect_string)
138
+ inspect_string.gsub(/(\S)=>(\S)/, '\1 => \2')
139
+ end
140
+ module_function :improve_hash_formatting
141
+ end
142
+
143
+ include HashFormatting
144
+
145
+ # @api private
146
+ # Provides default implementations of failure messages, based on the `description`.
147
+ module DefaultFailureMessages
148
+ # @api private
149
+ # Provides a good generic failure message. Based on `description`.
150
+ # When subclassing, if you are not satisfied with this failure message
151
+ # you often only need to override `description`.
152
+ # @return [String]
153
+ def failure_message
154
+ "expected #{description_of @actual} to #{description}".dup
155
+ end
156
+
157
+ # @api private
158
+ # Provides a good generic negative failure message. Based on `description`.
159
+ # When subclassing, if you are not satisfied with this failure message
160
+ # you often only need to override `description`.
161
+ # @return [String]
162
+ def failure_message_when_negated
163
+ "expected #{description_of @actual} not to #{description}".dup
164
+ end
165
+
166
+ # @private
167
+ def self.has_default_failure_messages?(matcher)
168
+ matcher.method(:failure_message).owner == self &&
169
+ matcher.method(:failure_message_when_negated).owner == self
170
+ rescue NameError
171
+ false
172
+ end
173
+ end
174
+
175
+ include DefaultFailureMessages
176
+ end
177
+ end
178
+ end
179
+ end