rspec-expectations 2.11.3 → 3.11.0

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 (152) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +1 -1
  5. data/Changelog.md +1026 -21
  6. data/{License.txt → LICENSE.md} +5 -3
  7. data/README.md +174 -78
  8. data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
  9. data/lib/rspec/expectations/configuration.rb +230 -0
  10. data/lib/rspec/expectations/expectation_target.rb +130 -55
  11. data/lib/rspec/expectations/fail_with.rb +17 -33
  12. data/lib/rspec/expectations/failure_aggregator.rb +212 -0
  13. data/lib/rspec/expectations/handler.rb +163 -29
  14. data/lib/rspec/expectations/minitest_integration.rb +58 -0
  15. data/lib/rspec/expectations/syntax.rb +68 -54
  16. data/lib/rspec/expectations/version.rb +1 -1
  17. data/lib/rspec/expectations.rb +59 -24
  18. data/lib/rspec/matchers/aliased_matcher.rb +116 -0
  19. data/lib/rspec/matchers/built_in/all.rb +86 -0
  20. data/lib/rspec/matchers/built_in/base_matcher.rb +150 -20
  21. data/lib/rspec/matchers/built_in/be.rb +115 -109
  22. data/lib/rspec/matchers/built_in/be_between.rb +77 -0
  23. data/lib/rspec/matchers/built_in/be_instance_of.rb +16 -1
  24. data/lib/rspec/matchers/built_in/be_kind_of.rb +10 -1
  25. data/lib/rspec/matchers/built_in/be_within.rb +43 -17
  26. data/lib/rspec/matchers/built_in/change.rb +392 -75
  27. data/lib/rspec/matchers/built_in/compound.rb +290 -0
  28. data/lib/rspec/matchers/built_in/contain_exactly.rb +302 -0
  29. data/lib/rspec/matchers/built_in/count_expectation.rb +169 -0
  30. data/lib/rspec/matchers/built_in/cover.rb +3 -0
  31. data/lib/rspec/matchers/built_in/eq.rb +26 -8
  32. data/lib/rspec/matchers/built_in/eql.rb +19 -8
  33. data/lib/rspec/matchers/built_in/equal.rb +56 -19
  34. data/lib/rspec/matchers/built_in/exist.rb +74 -10
  35. data/lib/rspec/matchers/built_in/has.rb +141 -22
  36. data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
  37. data/lib/rspec/matchers/built_in/include.rb +175 -20
  38. data/lib/rspec/matchers/built_in/match.rb +95 -1
  39. data/lib/rspec/matchers/built_in/operators.rb +128 -0
  40. data/lib/rspec/matchers/built_in/output.rb +207 -0
  41. data/lib/rspec/matchers/built_in/raise_error.rb +212 -38
  42. data/lib/rspec/matchers/built_in/respond_to.rb +155 -29
  43. data/lib/rspec/matchers/built_in/satisfy.rb +39 -9
  44. data/lib/rspec/matchers/built_in/start_or_end_with.rb +94 -0
  45. data/lib/rspec/matchers/built_in/throw_symbol.rb +58 -14
  46. data/lib/rspec/matchers/built_in/yield.rb +252 -98
  47. data/lib/rspec/matchers/built_in.rb +47 -33
  48. data/lib/rspec/matchers/composable.rb +171 -0
  49. data/lib/rspec/matchers/dsl.rb +530 -10
  50. data/lib/rspec/matchers/english_phrasing.rb +58 -0
  51. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +82 -0
  52. data/lib/rspec/matchers/fail_matchers.rb +42 -0
  53. data/lib/rspec/matchers/generated_descriptions.rb +15 -10
  54. data/lib/rspec/matchers/matcher_delegator.rb +35 -0
  55. data/lib/rspec/matchers/matcher_protocol.rb +105 -0
  56. data/lib/rspec/matchers.rb +604 -252
  57. data.tar.gz.sig +0 -0
  58. metadata +178 -278
  59. metadata.gz.sig +0 -0
  60. data/features/README.md +0 -49
  61. data/features/Upgrade.md +0 -53
  62. data/features/built_in_matchers/README.md +0 -90
  63. data/features/built_in_matchers/be.feature +0 -173
  64. data/features/built_in_matchers/be_within.feature +0 -46
  65. data/features/built_in_matchers/cover.feature +0 -45
  66. data/features/built_in_matchers/end_with.feature +0 -46
  67. data/features/built_in_matchers/equality.feature +0 -145
  68. data/features/built_in_matchers/exist.feature +0 -43
  69. data/features/built_in_matchers/expect_change.feature +0 -59
  70. data/features/built_in_matchers/expect_error.feature +0 -138
  71. data/features/built_in_matchers/have.feature +0 -103
  72. data/features/built_in_matchers/include.feature +0 -121
  73. data/features/built_in_matchers/match.feature +0 -50
  74. data/features/built_in_matchers/operators.feature +0 -221
  75. data/features/built_in_matchers/predicates.feature +0 -128
  76. data/features/built_in_matchers/respond_to.feature +0 -78
  77. data/features/built_in_matchers/satisfy.feature +0 -31
  78. data/features/built_in_matchers/start_with.feature +0 -46
  79. data/features/built_in_matchers/throw_symbol.feature +0 -85
  80. data/features/built_in_matchers/types.feature +0 -114
  81. data/features/built_in_matchers/yield.feature +0 -146
  82. data/features/custom_matchers/access_running_example.feature +0 -53
  83. data/features/custom_matchers/define_diffable_matcher.feature +0 -27
  84. data/features/custom_matchers/define_matcher.feature +0 -340
  85. data/features/custom_matchers/define_matcher_outside_rspec.feature +0 -38
  86. data/features/custom_matchers/define_matcher_with_fluent_interface.feature +0 -24
  87. data/features/customized_message.feature +0 -22
  88. data/features/diffing.feature +0 -85
  89. data/features/implicit_docstrings.feature +0 -52
  90. data/features/step_definitions/additional_cli_steps.rb +0 -22
  91. data/features/support/env.rb +0 -5
  92. data/features/syntax_configuration.feature +0 -68
  93. data/features/test_frameworks/test_unit.feature +0 -46
  94. data/lib/rspec/expectations/deprecation.rb +0 -38
  95. data/lib/rspec/expectations/differ.rb +0 -81
  96. data/lib/rspec/expectations/errors.rb +0 -9
  97. data/lib/rspec/expectations/extensions/array.rb +0 -9
  98. data/lib/rspec/expectations/extensions/object.rb +0 -39
  99. data/lib/rspec/expectations/extensions.rb +0 -2
  100. data/lib/rspec/matchers/be_close.rb +0 -9
  101. data/lib/rspec/matchers/built_in/have.rb +0 -108
  102. data/lib/rspec/matchers/built_in/match_array.rb +0 -45
  103. data/lib/rspec/matchers/built_in/start_and_end_with.rb +0 -48
  104. data/lib/rspec/matchers/compatibility.rb +0 -14
  105. data/lib/rspec/matchers/configuration.rb +0 -66
  106. data/lib/rspec/matchers/extensions/instance_eval_with_args.rb +0 -39
  107. data/lib/rspec/matchers/matcher.rb +0 -299
  108. data/lib/rspec/matchers/method_missing.rb +0 -12
  109. data/lib/rspec/matchers/operator_matcher.rb +0 -84
  110. data/lib/rspec/matchers/pretty.rb +0 -60
  111. data/lib/rspec-expectations.rb +0 -1
  112. data/spec/rspec/expectations/differ_spec.rb +0 -153
  113. data/spec/rspec/expectations/expectation_target_spec.rb +0 -65
  114. data/spec/rspec/expectations/extensions/kernel_spec.rb +0 -67
  115. data/spec/rspec/expectations/fail_with_spec.rb +0 -70
  116. data/spec/rspec/expectations/handler_spec.rb +0 -206
  117. data/spec/rspec/matchers/base_matcher_spec.rb +0 -60
  118. data/spec/rspec/matchers/be_close_spec.rb +0 -22
  119. data/spec/rspec/matchers/be_instance_of_spec.rb +0 -40
  120. data/spec/rspec/matchers/be_kind_of_spec.rb +0 -37
  121. data/spec/rspec/matchers/be_spec.rb +0 -452
  122. data/spec/rspec/matchers/be_within_spec.rb +0 -80
  123. data/spec/rspec/matchers/change_spec.rb +0 -528
  124. data/spec/rspec/matchers/configuration_spec.rb +0 -202
  125. data/spec/rspec/matchers/cover_spec.rb +0 -69
  126. data/spec/rspec/matchers/description_generation_spec.rb +0 -176
  127. data/spec/rspec/matchers/dsl_spec.rb +0 -57
  128. data/spec/rspec/matchers/eq_spec.rb +0 -54
  129. data/spec/rspec/matchers/eql_spec.rb +0 -41
  130. data/spec/rspec/matchers/equal_spec.rb +0 -60
  131. data/spec/rspec/matchers/exist_spec.rb +0 -110
  132. data/spec/rspec/matchers/has_spec.rb +0 -118
  133. data/spec/rspec/matchers/have_spec.rb +0 -461
  134. data/spec/rspec/matchers/include_spec.rb +0 -367
  135. data/spec/rspec/matchers/match_array_spec.rb +0 -124
  136. data/spec/rspec/matchers/match_spec.rb +0 -61
  137. data/spec/rspec/matchers/matcher_spec.rb +0 -434
  138. data/spec/rspec/matchers/matchers_spec.rb +0 -31
  139. data/spec/rspec/matchers/method_missing_spec.rb +0 -24
  140. data/spec/rspec/matchers/operator_matcher_spec.rb +0 -221
  141. data/spec/rspec/matchers/raise_error_spec.rb +0 -344
  142. data/spec/rspec/matchers/respond_to_spec.rb +0 -295
  143. data/spec/rspec/matchers/satisfy_spec.rb +0 -44
  144. data/spec/rspec/matchers/start_with_end_with_spec.rb +0 -182
  145. data/spec/rspec/matchers/throw_symbol_spec.rb +0 -116
  146. data/spec/rspec/matchers/yield_spec.rb +0 -402
  147. data/spec/spec_helper.rb +0 -27
  148. data/spec/support/classes.rb +0 -56
  149. data/spec/support/in_sub_process.rb +0 -31
  150. data/spec/support/matchers.rb +0 -22
  151. data/spec/support/ruby_version.rb +0 -10
  152. data/spec/support/shared_examples.rb +0 -13
@@ -1,7 +1,9 @@
1
- (The MIT License)
1
+ The MIT License (MIT)
2
+ =====================
2
3
 
3
- Copyright (c) 2006 David Chelimsky, The RSpec Development Team
4
- Copyright (c) 2005 Steven Baker
4
+ * Copyright © 2012 David Chelimsky, Myron Marston
5
+ * Copyright © 2006 David Chelimsky, The RSpec Development Team
6
+ * Copyright © 2005 Steven Baker
5
7
 
6
8
  Permission is hereby granted, free of charge, to any person obtaining
7
9
  a copy of this software and associated documentation files (the
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/badge.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
- account.balance.should 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,17 +15,40 @@ 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:
24
49
 
25
50
  ```ruby
26
- describe Order do
51
+ RSpec.describe Order do
27
52
  it "sums the prices of the items in its line items" do
28
53
  order = Order.new
29
54
  order.add_entry(LineItem.new(:item => Item.new(
@@ -38,8 +63,7 @@ describe Order do
38
63
  end
39
64
  ```
40
65
 
41
- The `describe` and `it` methods come from rspec-core. The `Order`, `LineItem`,
42
- and `Item` classes would be from _your_ code. The last line of the example
66
+ The `describe` and `it` methods come from rspec-core. The `Order`, `LineItem`, `Item` and `Money` classes would be from _your_ code. The last line of the example
43
67
  expresses an expected outcome. If `order.total == Money.new(5.55, :USD)`, then
44
68
  the example passes. If not, it fails with a message like:
45
69
 
@@ -51,48 +75,56 @@ the example passes. If not, it fails with a message like:
51
75
  ### Equivalence
52
76
 
53
77
  ```ruby
54
- actual.should eq(expected) # passes if actual == expected
55
- actual.should == expected # passes if actual == expected
56
- actual.should eql(expected) # passes if actual.eql?(expected)
78
+ expect(actual).to eq(expected) # passes if actual == expected
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))
57
81
  ```
58
82
 
83
+ Note: The new `expect` syntax no longer supports the `==` matcher.
84
+
59
85
  ### Identity
60
86
 
61
87
  ```ruby
62
- actual.should be(expected) # passes if actual.equal?(expected)
63
- actual.should equal(expected) # passes if actual.equal?(expected)
88
+ expect(actual).to be(expected) # passes if actual.equal?(expected)
89
+ expect(actual).to equal(expected) # passes if actual.equal?(expected)
64
90
  ```
65
91
 
66
92
  ### Comparisons
67
93
 
68
94
  ```ruby
69
- actual.should be > expected
70
- actual.should be >= expected
71
- actual.should be <= expected
72
- actual.should be < expected
73
- actual.should be_within(delta).of(expected)
95
+ expect(actual).to be > expected
96
+ expect(actual).to be >= expected
97
+ expect(actual).to be <= expected
98
+ expect(actual).to be < expected
99
+ expect(actual).to be_within(delta).of(expected)
74
100
  ```
75
101
 
76
102
  ### Regular expressions
77
103
 
78
104
  ```ruby
79
- actual.should =~ /expression/
80
- actual.should match(/expression/)
105
+ expect(actual).to match(/expression/)
81
106
  ```
82
107
 
108
+ Note: The new `expect` syntax no longer supports the `=~` matcher.
109
+
83
110
  ### Types/classes
84
111
 
85
112
  ```ruby
86
- actual.should be_an_instance_of(expected)
87
- actual.should be_a_kind_of(expected)
113
+ expect(actual).to be_an_instance_of(expected) # passes if actual.class == expected
114
+ expect(actual).to be_a(expected) # passes if actual.kind_of?(expected)
115
+ expect(actual).to be_an(expected) # an alias for be_a
116
+ expect(actual).to be_a_kind_of(expected) # another alias
88
117
  ```
89
118
 
90
119
  ### Truthiness
91
120
 
92
121
  ```ruby
93
- actual.should be_true # passes if actual is truthy (not nil or false)
94
- actual.should be_false # passes if actual is falsy (nil or false)
95
- actual.should 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
96
128
  ```
97
129
 
98
130
  ### Expecting errors
@@ -120,7 +152,7 @@ expect { |b| 5.tap(&b) }.to yield_control # passes regardless of yielded args
120
152
  expect { |b| yield_if_true(true, &b) }.to yield_with_no_args # passes only if no args are yielded
121
153
 
122
154
  expect { |b| 5.tap(&b) }.to yield_with_args(5)
123
- expect { |b| 5.tap(&b) }.to yield_with_args(Fixnum)
155
+ expect { |b| 5.tap(&b) }.to yield_with_args(Integer)
124
156
  expect { |b| "a string".tap(&b) }.to yield_with_args(/str/)
125
157
 
126
158
  expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3)
@@ -130,95 +162,159 @@ expect { |b| { :a => 1, :b => 2 }.each(&b) }.to yield_successive_args([:a, 1], [
130
162
  ### Predicate matchers
131
163
 
132
164
  ```ruby
133
- actual.should be_xxx # passes if actual.xxx?
134
- actual.should have_xxx(:arg) # passes if actual.has_xxx?(:arg)
165
+ expect(actual).to be_xxx # passes if actual.xxx?
166
+ expect(actual).to have_xxx(:arg) # passes if actual.has_xxx?(:arg)
135
167
  ```
136
168
 
137
169
  ### Ranges (Ruby >= 1.9 only)
138
170
 
139
171
  ```ruby
140
- (1..10).should cover(3)
172
+ expect(1..10).to cover(3)
141
173
  ```
142
174
 
143
175
  ### Collection membership
144
176
 
145
177
  ```ruby
146
- actual.should include(expected)
147
- actual.should start_with(expected)
148
- actual.should end_with(expected)
178
+ # exact order, entire collection
179
+ expect(actual).to eq(expected)
180
+
181
+ # exact order, partial collection (based on an exact position)
182
+ expect(actual).to start_with(expected)
183
+ expect(actual).to end_with(expected)
184
+
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)
149
194
  ```
150
195
 
151
196
  #### Examples
152
197
 
153
198
  ```ruby
154
- [1,2,3].should include(1)
155
- [1,2,3].should include(1, 2)
156
- [1,2,3].should start_with(1)
157
- [1,2,3].should start_with(1,2)
158
- [1,2,3].should end_with(3)
159
- [1,2,3].should end_with(2,3)
160
- {:a => 'b'}.should include(:a => 'b')
161
- "this string".should include("is str")
162
- "this string".should start_with("this")
163
- "this string".should end_with("ring")
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')])
164
217
  ```
165
218
 
166
- ## `expect` syntax
219
+ ## `should` syntax
167
220
 
168
- In addition to the `should` syntax, rspec-expectations supports
169
- a new `expect` syntax as of version 2.11.0:
221
+ In addition to the `expect` syntax, rspec-expectations continues to support the
222
+ `should` syntax:
170
223
 
171
224
  ```ruby
172
- expect(actual).to eq expected
173
- expect(actual).to be > 3
174
- expect([1, 2, 3]).to_not include 4
225
+ actual.should eq expected
226
+ actual.should be > 3
227
+ [1, 2, 3].should_not include 4
175
228
  ```
176
229
 
177
- If you want your project to only use one of these syntaxes, you can
178
- configure it:
230
+ See [detailed information on the `should` syntax and its usage.](https://github.com/rspec/rspec-expectations/blob/main/Should.md)
179
231
 
180
- ```ruby
181
- RSpec.configure do |config|
182
- config.expect_with :rspec do |c|
183
- c.syntax = :expect
184
- # or
185
- c.syntax = :should
186
- # or
187
- c.syntax = [:should, :expect]
188
- end
189
- end
232
+ ## Compound Matcher Expressions
233
+
234
+ You can also create compound matcher expressions using `and` or `or`:
235
+
236
+ ``` ruby
237
+ expect(alphabet).to start_with("a").and end_with("z")
238
+ expect(stoplight.color).to eq("red").or eq("green").or eq("yellow")
190
239
  ```
191
240
 
192
- See
193
- [RSpec::Expectations::Syntax#expect](http://rubydoc.info/gems/rspec-expectations/RSpec/Expectations/Syntax:expect)
194
- for more information.
241
+ ## Composing Matchers
195
242
 
196
- ### Motivation for `expect`
243
+ Many of the built-in matchers are designed to take matchers as
244
+ arguments, to allow you to flexibly specify only the essential
245
+ aspects of an object or data structure. In addition, all of the
246
+ built-in matchers have one or more aliases that provide better
247
+ phrasing for when they are used as arguments to another matcher.
197
248
 
198
- We added the `expect` syntax to resolve some edge case issues, most notably
199
- that objects whose definitions wipe out all but a few methods were throwing
200
- `should` and `should_not` away. `expect` solves that by not monkey patching
201
- those methods onto `Kernel` (or any global object).
249
+ ### Examples
202
250
 
203
- See
204
- [http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax](http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax)
205
- for a detailed explanation.
251
+ ```ruby
252
+ expect { k += 1.05 }.to change { k }.by( a_value_within(0.1).of(1.0) )
253
+
254
+ expect { s = "barn" }.to change { s }
255
+ .from( a_string_matching(/foo/) )
256
+ .to( a_string_matching(/bar/) )
257
+
258
+ expect(["barn", 2.45]).to contain_exactly(
259
+ a_value_within(0.1).of(2.5),
260
+ a_string_starting_with("bar")
261
+ )
262
+
263
+ expect(["barn", "food", 2.45]).to end_with(
264
+ a_string_matching("foo"),
265
+ a_value > 2
266
+ )
267
+
268
+ expect(["barn", 2.45]).to include( a_string_starting_with("bar") )
269
+
270
+ expect(:a => "food", :b => "good").to include(:a => a_string_matching(/foo/))
271
+
272
+ hash = {
273
+ :a => {
274
+ :b => ["foo", 5],
275
+ :c => { :d => 2.05 }
276
+ }
277
+ }
278
+
279
+ expect(hash).to match(
280
+ :a => {
281
+ :b => a_collection_containing_exactly(
282
+ a_string_starting_with("f"),
283
+ an_instance_of(Integer)
284
+ ),
285
+ :c => { :d => (a_value < 3) }
286
+ }
287
+ )
288
+
289
+ expect { |probe|
290
+ [1, 2, 3].each(&probe)
291
+ }.to yield_successive_args( a_value < 2, 2, a_value > 2 )
292
+ ```
206
293
 
207
- ### One-liners
294
+ ## Usage outside rspec-core
208
295
 
209
- The one-liner syntax supported by
210
- [rspec-core](http://rubydoc.info/gems/rspec-core) uses `should` even when
211
- `config.syntax = :expect`. It reads better than the alternative, and does not
212
- require a global monkey patch:
296
+ You always need to load `rspec/expectations` even if you only want to use one part of the library:
213
297
 
214
298
  ```ruby
215
- describe User do
216
- it { should validate_presence_of :email }
299
+ require 'rspec/expectations'
300
+ ```
301
+
302
+ Then simply include `RSpec::Matchers` in any class:
303
+
304
+ ```ruby
305
+ class MyClass
306
+ include RSpec::Matchers
307
+
308
+ def do_something(arg)
309
+ expect(arg).to be > 0
310
+ # do other stuff
311
+ end
217
312
  end
218
313
  ```
219
314
 
220
315
  ## Also see
221
316
 
222
- * [http://github.com/rspec/rspec](http://github.com/rspec/rspec)
223
- * [http://github.com/rspec/rspec-core](http://github.com/rspec/rspec-core)
224
- * [http://github.com/rspec/rspec-mocks](http://github.com/rspec/rspec-mocks)
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