rspec-expectations 2.11.3 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
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