rspec-expectations 3.0.4 → 3.12.3

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 (59) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +1 -1
  5. data/Changelog.md +530 -5
  6. data/{License.txt → LICENSE.md} +5 -4
  7. data/README.md +73 -31
  8. data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
  9. data/lib/rspec/expectations/configuration.rb +96 -1
  10. data/lib/rspec/expectations/expectation_target.rb +82 -38
  11. data/lib/rspec/expectations/fail_with.rb +11 -6
  12. data/lib/rspec/expectations/failure_aggregator.rb +229 -0
  13. data/lib/rspec/expectations/handler.rb +36 -15
  14. data/lib/rspec/expectations/minitest_integration.rb +43 -2
  15. data/lib/rspec/expectations/syntax.rb +5 -5
  16. data/lib/rspec/expectations/version.rb +1 -1
  17. data/lib/rspec/expectations.rb +15 -1
  18. data/lib/rspec/matchers/aliased_matcher.rb +79 -4
  19. data/lib/rspec/matchers/built_in/all.rb +11 -0
  20. data/lib/rspec/matchers/built_in/base_matcher.rb +111 -28
  21. data/lib/rspec/matchers/built_in/be.rb +28 -114
  22. data/lib/rspec/matchers/built_in/be_between.rb +1 -1
  23. data/lib/rspec/matchers/built_in/be_instance_of.rb +5 -1
  24. data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -1
  25. data/lib/rspec/matchers/built_in/be_within.rb +5 -12
  26. data/lib/rspec/matchers/built_in/change.rb +171 -63
  27. data/lib/rspec/matchers/built_in/compound.rb +201 -30
  28. data/lib/rspec/matchers/built_in/contain_exactly.rb +73 -12
  29. data/lib/rspec/matchers/built_in/count_expectation.rb +169 -0
  30. data/lib/rspec/matchers/built_in/eq.rb +3 -38
  31. data/lib/rspec/matchers/built_in/eql.rb +2 -2
  32. data/lib/rspec/matchers/built_in/equal.rb +3 -3
  33. data/lib/rspec/matchers/built_in/exist.rb +7 -3
  34. data/lib/rspec/matchers/built_in/has.rb +93 -30
  35. data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
  36. data/lib/rspec/matchers/built_in/include.rb +133 -25
  37. data/lib/rspec/matchers/built_in/match.rb +79 -2
  38. data/lib/rspec/matchers/built_in/operators.rb +14 -5
  39. data/lib/rspec/matchers/built_in/output.rb +59 -2
  40. data/lib/rspec/matchers/built_in/raise_error.rb +130 -27
  41. data/lib/rspec/matchers/built_in/respond_to.rb +117 -15
  42. data/lib/rspec/matchers/built_in/satisfy.rb +28 -14
  43. data/lib/rspec/matchers/built_in/{start_and_end_with.rb → start_or_end_with.rb} +20 -8
  44. data/lib/rspec/matchers/built_in/throw_symbol.rb +15 -5
  45. data/lib/rspec/matchers/built_in/yield.rb +129 -156
  46. data/lib/rspec/matchers/built_in.rb +5 -3
  47. data/lib/rspec/matchers/composable.rb +24 -36
  48. data/lib/rspec/matchers/dsl.rb +203 -37
  49. data/lib/rspec/matchers/english_phrasing.rb +58 -0
  50. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +82 -0
  51. data/lib/rspec/matchers/fail_matchers.rb +42 -0
  52. data/lib/rspec/matchers/generated_descriptions.rb +1 -2
  53. data/lib/rspec/matchers/matcher_delegator.rb +3 -4
  54. data/lib/rspec/matchers/matcher_protocol.rb +105 -0
  55. data/lib/rspec/matchers.rb +267 -144
  56. data.tar.gz.sig +0 -0
  57. metadata +71 -49
  58. metadata.gz.sig +0 -0
  59. data/lib/rspec/matchers/pretty.rb +0 -77
data/README.md CHANGED
@@ -1,9 +1,11 @@
1
- # RSpec Expectations [![Build Status](https://secure.travis-ci.org/rspec/rspec-expectations.png?branch=master)](http://travis-ci.org/rspec/rspec-expectations) [![Code Climate](https://codeclimate.com/github/rspec/rspec-expectations.png)](https://codeclimate.com/github/rspec/rspec-expectations)
1
+ # RSpec Expectations [![Build Status](https://github.com/rspec/rspec-expectations/workflows/RSpec%20CI/badge.svg)](https://github.com/rspec/rspec-expectations/actions) [![Code Climate](https://codeclimate.com/github/rspec/rspec-expectations.svg)](https://codeclimate.com/github/rspec/rspec-expectations)
2
2
 
3
3
  RSpec::Expectations lets you express expected outcomes on an object in an
4
4
  example.
5
5
 
6
- expect(account.balance).to eq(Money.new(37.42, :USD))
6
+ ```ruby
7
+ expect(account.balance).to eq(Money.new(37.42, :USD))
8
+ ```
7
9
 
8
10
  ## Install
9
11
 
@@ -13,11 +15,34 @@ rspec-core and rspec-mocks):
13
15
 
14
16
  gem install rspec
15
17
 
18
+ Want to run against the `main` branch? You'll need to include the dependent
19
+ RSpec repos as well. Add the following to your `Gemfile`:
20
+
21
+ ```ruby
22
+ %w[rspec-core rspec-expectations rspec-mocks rspec-support].each do |lib|
23
+ gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => 'main'
24
+ end
25
+ ```
26
+
16
27
  If you want to use rspec-expectations with another tool, like Test::Unit,
17
28
  Minitest, or Cucumber, you can install it directly:
18
29
 
19
30
  gem install rspec-expectations
20
31
 
32
+ ## Contributing
33
+
34
+ Once you've set up the environment, you'll need to cd into the working
35
+ directory of whichever repo you want to work in. From there you can run the
36
+ specs and cucumber features, and make patches.
37
+
38
+ NOTE: You do not need to use rspec-dev to work on a specific RSpec repo. You
39
+ can treat each RSpec repo as an independent project.
40
+
41
+ - [Build details](BUILD_DETAIL.md)
42
+ - [Code of Conduct](CODE_OF_CONDUCT.md)
43
+ - [Detailed contributing guide](CONTRIBUTING.md)
44
+ - [Development setup guide](DEVELOPMENT.md)
45
+
21
46
  ## Basic usage
22
47
 
23
48
  Here's an example using rspec-core:
@@ -52,6 +77,7 @@ the example passes. If not, it fails with a message like:
52
77
  ```ruby
53
78
  expect(actual).to eq(expected) # passes if actual == expected
54
79
  expect(actual).to eql(expected) # passes if actual.eql?(expected)
80
+ expect(actual).not_to eql(not_expected) # passes if not(actual.eql?(expected))
55
81
  ```
56
82
 
57
83
  Note: The new `expect` syntax no longer supports the `==` matcher.
@@ -85,7 +111,7 @@ Note: The new `expect` syntax no longer supports the `=~` matcher.
85
111
 
86
112
  ```ruby
87
113
  expect(actual).to be_an_instance_of(expected) # passes if actual.class == expected
88
- expect(actual).to be_a(expected) # passes if actual.is_a?(expected)
114
+ expect(actual).to be_a(expected) # passes if actual.kind_of?(expected)
89
115
  expect(actual).to be_an(expected) # an alias for be_a
90
116
  expect(actual).to be_a_kind_of(expected) # another alias
91
117
  ```
@@ -93,11 +119,12 @@ expect(actual).to be_a_kind_of(expected) # another alias
93
119
  ### Truthiness
94
120
 
95
121
  ```ruby
96
- expect(actual).to be_truthy # passes if actual is truthy (not nil or false)
97
- expect(actual).to be true # passes if actual == true
98
- expect(actual).to be_falsy # passes if actual is falsy (nil or false)
99
- expect(actual).to be false # passes if actual == false
100
- expect(actual).to be_nil # passes if actual is nil
122
+ expect(actual).to be_truthy # passes if actual is truthy (not nil or false)
123
+ expect(actual).to be true # passes if actual == true
124
+ expect(actual).to be_falsy # passes if actual is falsy (nil or false)
125
+ expect(actual).to be false # passes if actual == false
126
+ expect(actual).to be_nil # passes if actual is nil
127
+ expect(actual).to_not be_nil # passes if actual is not nil
101
128
  ```
102
129
 
103
130
  ### Expecting errors
@@ -125,7 +152,7 @@ expect { |b| 5.tap(&b) }.to yield_control # passes regardless of yielded args
125
152
  expect { |b| yield_if_true(true, &b) }.to yield_with_no_args # passes only if no args are yielded
126
153
 
127
154
  expect { |b| 5.tap(&b) }.to yield_with_args(5)
128
- expect { |b| 5.tap(&b) }.to yield_with_args(Fixnum)
155
+ expect { |b| 5.tap(&b) }.to yield_with_args(Integer)
129
156
  expect { |b| "a string".tap(&b) }.to yield_with_args(/str/)
130
157
 
131
158
  expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3)
@@ -148,30 +175,45 @@ expect(1..10).to cover(3)
148
175
  ### Collection membership
149
176
 
150
177
  ```ruby
151
- expect(actual).to include(expected)
178
+ # exact order, entire collection
179
+ expect(actual).to eq(expected)
180
+
181
+ # exact order, partial collection (based on an exact position)
152
182
  expect(actual).to start_with(expected)
153
183
  expect(actual).to end_with(expected)
154
184
 
155
- expect(actual).to contain_exactly(individual, items)
156
- # ...which is the same as:
157
- expect(actual).to match_array(expected_array)
185
+ # any order, entire collection
186
+ expect(actual).to match_array(expected)
187
+
188
+ # You can also express this by passing the expected elements
189
+ # as individual arguments
190
+ expect(actual).to contain_exactly(expected_element1, expected_element2)
191
+
192
+ # any order, partial collection
193
+ expect(actual).to include(expected)
158
194
  ```
159
195
 
160
196
  #### Examples
161
197
 
162
198
  ```ruby
163
- expect([1, 2, 3]).to include(1)
164
- expect([1, 2, 3]).to include(1, 2)
165
- expect([1, 2, 3]).to start_with(1)
166
- expect([1, 2, 3]).to start_with(1, 2)
167
- expect([1, 2, 3]).to end_with(3)
168
- expect([1, 2, 3]).to end_with(2, 3)
169
- expect({:a => 'b'}).to include(:a => 'b')
170
- expect("this string").to include("is str")
171
- expect("this string").to start_with("this")
172
- expect("this string").to end_with("ring")
173
- expect([1, 2, 3]).to contain_exactly(2, 3, 1)
174
- expect([1, 2, 3]).to match_array([3, 2, 1])
199
+ expect([1, 2, 3]).to eq([1, 2, 3]) # Order dependent equality check
200
+ expect([1, 2, 3]).to include(1) # Exact ordering, partial collection matches
201
+ expect([1, 2, 3]).to include(2, 3) #
202
+ expect([1, 2, 3]).to start_with(1) # As above, but from the start of the collection
203
+ expect([1, 2, 3]).to start_with(1, 2) #
204
+ expect([1, 2, 3]).to end_with(3) # As above but from the end of the collection
205
+ expect([1, 2, 3]).to end_with(2, 3) #
206
+ expect({:a => 'b'}).to include(:a => 'b') # Matching within hashes
207
+ expect("this string").to include("is str") # Matching within strings
208
+ expect("this string").to start_with("this") #
209
+ expect("this string").to end_with("ring") #
210
+ expect([1, 2, 3]).to contain_exactly(2, 3, 1) # Order independent matches
211
+ expect([1, 2, 3]).to match_array([3, 2, 1]) #
212
+
213
+ # Order dependent compound matchers
214
+ expect(
215
+ [{:a => 'hash'},{:a => 'another'}]
216
+ ).to match([a_hash_including(:a => 'hash'), a_hash_including(:a => 'another')])
175
217
  ```
176
218
 
177
219
  ## `should` syntax
@@ -185,7 +227,7 @@ actual.should be > 3
185
227
  [1, 2, 3].should_not include 4
186
228
  ```
187
229
 
188
- See [detailed information on the `should` syntax and its usage.](https://github.com/rspec/rspec-expectations/blob/master/Should.md)
230
+ See [detailed information on the `should` syntax and its usage.](https://github.com/rspec/rspec-expectations/blob/main/Should.md)
189
231
 
190
232
  ## Compound Matcher Expressions
191
233
 
@@ -238,7 +280,7 @@ expect(hash).to match(
238
280
  :a => {
239
281
  :b => a_collection_containing_exactly(
240
282
  a_string_starting_with("f"),
241
- an_instance_of(Fixnum)
283
+ an_instance_of(Integer)
242
284
  ),
243
285
  :c => { :d => (a_value < 3) }
244
286
  }
@@ -272,7 +314,7 @@ end
272
314
 
273
315
  ## Also see
274
316
 
275
- * [http://github.com/rspec/rspec](http://github.com/rspec/rspec)
276
- * [http://github.com/rspec/rspec-core](http://github.com/rspec/rspec-core)
277
- * [http://github.com/rspec/rspec-mocks](http://github.com/rspec/rspec-mocks)
278
- * [http://github.com/rspec/rspec-collection_matchers](https://github.com/rspec/rspec-collection_matchers)
317
+ * [https://github.com/rspec/rspec](https://github.com/rspec/rspec)
318
+ * [https://github.com/rspec/rspec-core](https://github.com/rspec/rspec-core)
319
+ * [https://github.com/rspec/rspec-mocks](https://github.com/rspec/rspec-mocks)
320
+ * [https://github.com/rspec/rspec-rails](https://github.com/rspec/rspec-rails)
@@ -0,0 +1,253 @@
1
+ module RSpec
2
+ module Expectations
3
+ # @private
4
+ class BlockSnippetExtractor # rubocop:disable Metrics/ClassLength
5
+ # rubocop should properly handle `Struct.new {}` as an inner class definition.
6
+
7
+ attr_reader :proc, :method_name
8
+
9
+ def self.try_extracting_single_line_body_of(proc, method_name)
10
+ lines = new(proc, method_name).body_content_lines
11
+ return nil unless lines.count == 1
12
+ lines.first
13
+ rescue Error
14
+ nil
15
+ end
16
+
17
+ def initialize(proc, method_name)
18
+ @proc = proc
19
+ @method_name = method_name.to_s.freeze
20
+ end
21
+
22
+ # Ideally we should properly handle indentations of multiline snippet,
23
+ # but it's not implemented yet since because we use result of this method only when it's a
24
+ # single line and implementing the logic introduces additional complexity.
25
+ def body_content_lines
26
+ raw_body_lines.map(&:strip).reject(&:empty?)
27
+ end
28
+
29
+ private
30
+
31
+ def raw_body_lines
32
+ raw_body_snippet.split("\n")
33
+ end
34
+
35
+ def raw_body_snippet
36
+ block_token_extractor.body_tokens.map(&:string).join
37
+ end
38
+
39
+ def block_token_extractor
40
+ @block_token_extractor ||= BlockTokenExtractor.new(method_name, source, beginning_line_number)
41
+ end
42
+
43
+ if RSpec.respond_to?(:world)
44
+ def source
45
+ raise TargetNotFoundError unless File.exist?(file_path)
46
+ RSpec.world.source_from_file(file_path)
47
+ end
48
+ else
49
+ RSpec::Support.require_rspec_support 'source'
50
+ def source
51
+ raise TargetNotFoundError unless File.exist?(file_path)
52
+ @source ||= RSpec::Support::Source.from_file(file_path)
53
+ end
54
+ end
55
+
56
+ def file_path
57
+ source_location.first
58
+ end
59
+
60
+ def beginning_line_number
61
+ source_location.last
62
+ end
63
+
64
+ def source_location
65
+ proc.source_location || raise(TargetNotFoundError)
66
+ end
67
+
68
+ Error = Class.new(StandardError)
69
+ TargetNotFoundError = Class.new(Error)
70
+ AmbiguousTargetError = Class.new(Error)
71
+
72
+ # @private
73
+ # Performs extraction of block body snippet using tokens,
74
+ # which cannot be done with node information.
75
+ BlockTokenExtractor = Struct.new(:method_name, :source, :beginning_line_number) do
76
+ attr_reader :state, :body_tokens
77
+
78
+ def initialize(*)
79
+ super
80
+ parse!
81
+ end
82
+
83
+ private
84
+
85
+ def parse!
86
+ @state = :initial
87
+
88
+ catch(:finish) do
89
+ source.tokens.each do |token|
90
+ invoke_state_handler(token)
91
+ end
92
+ end
93
+ end
94
+
95
+ def finish!
96
+ throw :finish
97
+ end
98
+
99
+ def invoke_state_handler(token)
100
+ __send__("#{state}_state", token)
101
+ end
102
+
103
+ def initial_state(token)
104
+ @state = :after_method_call if token.location == block_locator.method_call_location
105
+ end
106
+
107
+ def after_method_call_state(token)
108
+ @state = :after_opener if handle_opener_token(token)
109
+ end
110
+
111
+ def after_opener_state(token)
112
+ if handle_closer_token(token)
113
+ finish_or_find_next_block_if_incorrect!
114
+ elsif pipe_token?(token)
115
+ finalize_pending_tokens!
116
+ @state = :after_beginning_of_args
117
+ else
118
+ pending_tokens << token
119
+ handle_opener_token(token)
120
+ @state = :after_beginning_of_body unless token.type == :on_sp
121
+ end
122
+ end
123
+
124
+ def after_beginning_of_args_state(token)
125
+ @state = :after_beginning_of_body if pipe_token?(token)
126
+ end
127
+
128
+ def after_beginning_of_body_state(token)
129
+ if handle_closer_token(token)
130
+ finish_or_find_next_block_if_incorrect!
131
+ else
132
+ pending_tokens << token
133
+ handle_opener_token(token)
134
+ end
135
+ end
136
+
137
+ def pending_tokens
138
+ @pending_tokens ||= []
139
+ end
140
+
141
+ def finalize_pending_tokens!
142
+ pending_tokens.freeze.tap do
143
+ @pending_tokens = nil
144
+ end
145
+ end
146
+
147
+ def finish_or_find_next_block_if_incorrect!
148
+ body_tokens = finalize_pending_tokens!
149
+
150
+ if correct_block?(body_tokens)
151
+ @body_tokens = body_tokens
152
+ finish!
153
+ else
154
+ @state = :after_method_call
155
+ end
156
+ end
157
+
158
+ def handle_opener_token(token)
159
+ opener_token?(token).tap do |boolean|
160
+ opener_token_stack.push(token) if boolean
161
+ end
162
+ end
163
+
164
+ def opener_token?(token)
165
+ token.type == :on_lbrace || (token.type == :on_kw && token.string == 'do')
166
+ end
167
+
168
+ def handle_closer_token(token)
169
+ if opener_token_stack.last.closed_by?(token)
170
+ opener_token_stack.pop
171
+ opener_token_stack.empty?
172
+ else
173
+ false
174
+ end
175
+ end
176
+
177
+ def opener_token_stack
178
+ @opener_token_stack ||= []
179
+ end
180
+
181
+ def pipe_token?(token)
182
+ token.type == :on_op && token.string == '|'
183
+ end
184
+
185
+ def correct_block?(body_tokens)
186
+ return true if block_locator.body_content_locations.empty?
187
+ content_location = block_locator.body_content_locations.first
188
+ content_location.between?(body_tokens.first.location, body_tokens.last.location)
189
+ end
190
+
191
+ def block_locator
192
+ @block_locator ||= BlockLocator.new(method_name, source, beginning_line_number)
193
+ end
194
+ end
195
+
196
+ # @private
197
+ # Locates target block with node information (semantics), which tokens don't have.
198
+ BlockLocator = Struct.new(:method_name, :source, :beginning_line_number) do
199
+ def method_call_location
200
+ @method_call_location ||= method_ident_node.location
201
+ end
202
+
203
+ def body_content_locations
204
+ @body_content_locations ||= block_body_node.map(&:location).compact
205
+ end
206
+
207
+ private
208
+
209
+ def method_ident_node
210
+ method_call_node = block_wrapper_node.children.first
211
+ method_call_node.find do |node|
212
+ method_ident_node?(node)
213
+ end
214
+ end
215
+
216
+ def block_body_node
217
+ block_node = block_wrapper_node.children[1]
218
+ block_node.children.last
219
+ end
220
+
221
+ def block_wrapper_node
222
+ case candidate_block_wrapper_nodes.size
223
+ when 1
224
+ candidate_block_wrapper_nodes.first
225
+ when 0
226
+ raise TargetNotFoundError
227
+ else
228
+ raise AmbiguousTargetError
229
+ end
230
+ end
231
+
232
+ def candidate_block_wrapper_nodes
233
+ @candidate_block_wrapper_nodes ||= candidate_method_ident_nodes.map do |method_ident_node|
234
+ block_wrapper_node = method_ident_node.each_ancestor.find { |node| node.type == :method_add_block }
235
+ next nil unless block_wrapper_node
236
+ method_call_node = block_wrapper_node.children.first
237
+ method_call_node.include?(method_ident_node) ? block_wrapper_node : nil
238
+ end.compact
239
+ end
240
+
241
+ def candidate_method_ident_nodes
242
+ source.nodes_by_line_number[beginning_line_number].select do |node|
243
+ method_ident_node?(node)
244
+ end
245
+ end
246
+
247
+ def method_ident_node?(node)
248
+ node.type == :@ident && node.args.first == method_name
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
@@ -18,6 +18,19 @@ module RSpec
18
18
  #
19
19
  # RSpec::Expectations.configuration
20
20
  class Configuration
21
+ # @private
22
+ FALSE_POSITIVE_BEHAVIOURS =
23
+ {
24
+ :warn => lambda { |message| RSpec.warning message },
25
+ :raise => lambda { |message| raise ArgumentError, message },
26
+ :nothing => lambda { |_| true },
27
+ }
28
+
29
+ def initialize
30
+ @on_potential_false_positives = :warn
31
+ @strict_predicate_matchers = false
32
+ end
33
+
21
34
  # Configures the supported syntax.
22
35
  # @param [Array<Symbol>, Symbol] values the syntaxes to enable
23
36
  # @example
@@ -44,6 +57,20 @@ module RSpec
44
57
  end
45
58
  end
46
59
 
60
+ # Configures the maximum character length that RSpec will print while
61
+ # formatting an object. You can set length to nil to prevent RSpec from
62
+ # doing truncation.
63
+ # @param [Fixnum] length the number of characters to limit the formatted output to.
64
+ # @example
65
+ # RSpec.configure do |rspec|
66
+ # rspec.expect_with :rspec do |c|
67
+ # c.max_formatted_output_length = 200
68
+ # end
69
+ # end
70
+ def max_formatted_output_length=(length)
71
+ RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = length
72
+ end
73
+
47
74
  # The list of configured syntaxes.
48
75
  # @return [Array<Symbol>] the list of configured syntaxes.
49
76
  # @example
@@ -71,7 +98,7 @@ module RSpec
71
98
  # Delegates to rspec-core's color option if rspec-core
72
99
  # is loaded; otherwise you can set it here.
73
100
  def color?
74
- @color
101
+ defined?(@color) && @color
75
102
  end
76
103
  end
77
104
 
@@ -107,6 +134,18 @@ module RSpec
107
134
  end
108
135
  end
109
136
 
137
+ # Sets if custom matcher descriptions and failure messages
138
+ # should include clauses from methods defined using `chain`.
139
+ # @param value [Boolean]
140
+ attr_writer :include_chain_clauses_in_custom_matcher_descriptions
141
+
142
+ # Indicates whether or not custom matcher descriptions and failure messages
143
+ # should include clauses from methods defined using `chain`. It is
144
+ # false by default for backwards compatibility.
145
+ def include_chain_clauses_in_custom_matcher_descriptions?
146
+ @include_chain_clauses_in_custom_matcher_descriptions ||= false
147
+ end
148
+
110
149
  # @private
111
150
  def reset_syntaxes_to_default
112
151
  self.syntax = [:should, :expect]
@@ -121,6 +160,62 @@ module RSpec
121
160
  backtrace
122
161
  end
123
162
  end
163
+
164
+ # Configures whether RSpec will warn about matcher use which will
165
+ # potentially cause false positives in tests.
166
+ #
167
+ # @param [Boolean] boolean
168
+ def warn_about_potential_false_positives=(boolean)
169
+ if boolean
170
+ self.on_potential_false_positives = :warn
171
+ elsif warn_about_potential_false_positives?
172
+ self.on_potential_false_positives = :nothing
173
+ else
174
+ # no-op, handler is something else
175
+ end
176
+ end
177
+ #
178
+ # Configures what RSpec will do about matcher use which will
179
+ # potentially cause false positives in tests.
180
+ #
181
+ # @param [Symbol] behavior can be set to :warn, :raise or :nothing
182
+ def on_potential_false_positives=(behavior)
183
+ unless FALSE_POSITIVE_BEHAVIOURS.key?(behavior)
184
+ raise ArgumentError, "Supported values are: #{FALSE_POSITIVE_BEHAVIOURS.keys}"
185
+ end
186
+ @on_potential_false_positives = behavior
187
+ end
188
+
189
+ # Configures RSpec to check predicate matchers to `be(true)` / `be(false)` (strict),
190
+ # or `be_truthy` / `be_falsey` (not strict).
191
+ # Historically, the default was `false`, but `true` is recommended.
192
+ def strict_predicate_matchers=(flag)
193
+ raise ArgumentError, "Pass `true` or `false`" unless flag == true || flag == false
194
+ @strict_predicate_matchers = flag
195
+ end
196
+
197
+ attr_reader :strict_predicate_matchers
198
+
199
+ def strict_predicate_matchers?
200
+ @strict_predicate_matchers
201
+ end
202
+
203
+ # Indicates what RSpec will do about matcher use which will
204
+ # potentially cause false positives in tests, generally you want to
205
+ # avoid such scenarios so this defaults to `true`.
206
+ attr_reader :on_potential_false_positives
207
+
208
+ # Indicates whether RSpec will warn about matcher use which will
209
+ # potentially cause false positives in tests, generally you want to
210
+ # avoid such scenarios so this defaults to `true`.
211
+ def warn_about_potential_false_positives?
212
+ on_potential_false_positives == :warn
213
+ end
214
+
215
+ # @private
216
+ def false_positives_handler
217
+ FALSE_POSITIVE_BEHAVIOURS.fetch(@on_potential_false_positives)
218
+ end
124
219
  end
125
220
 
126
221
  # The configuration object.