r_spec-clone 1.2.0 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e923f4f11f83ba71f2c7267393691edf8bf8ea9be1ae3598f57cb637d71c411
4
- data.tar.gz: 18d34b05058e5f17eca7c874f03d52fdac54e995858e1531ee5acff77ceed552
3
+ metadata.gz: 2e5c67c5d900bbe71f8e647c1fd1c4e5bd6a96e01b9db3d228918dedb755edd2
4
+ data.tar.gz: 3907ef8dab4a2bb8c751eacf281c5f643b2f3a9c774fbb71ec1cfc0707072dea
5
5
  SHA512:
6
- metadata.gz: 80419c1141a6efc61b8f47294d08c8932a416b8c983419a63afdab4bbf8a593d865c37ecb37f84e186459004cd7f807d87d5f99e5625ded0bb8fa885b2ad50d0
7
- data.tar.gz: 7796a4e33df45136434543f1f2dc2a0ae32a8d93d61bf11515ab0b8d3df7b300b78bd4f5976c913b777ea75da9500b85389268c51f4851eaf30fbe6012aa25fc
6
+ metadata.gz: 758790442ee21c56a0d40f98385ad7bdfbcbc88d527fc266f86641623fed9c172b68144e538a4565dd19e211b53b3412d847813978991853bfe2d8ab82a67cd7
7
+ data.tar.gz: fd4c45945f0f84de6361f68aaec5d84feb6f9983f27248b42abb5757b9c17a3fd15b00e4c0ab7314a6050d6234f8e3b8e5ef997d87df26b34e7f199322dc621d
data/README.md CHANGED
@@ -6,6 +6,7 @@ A minimalist __RSpec clone__ with all the essentials.
6
6
 
7
7
  ## Status
8
8
 
9
+ [![Home](https://img.shields.io/badge/Home-r--spec.dev-00af8b)](https://r-spec.dev/)
9
10
  [![Version](https://img.shields.io/github/v/tag/cyril/r_spec-clone.rb?label=Version&logo=github)](https://github.com/cyril/r_spec-clone.rb/releases)
10
11
  [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/cyril/r_spec-clone.rb/main)
11
12
  [![CI](https://github.com/cyril/r_spec-clone.rb/workflows/CI/badge.svg?branch=main)](https://github.com/cyril/r_spec-clone.rb/actions?query=workflow%3Aci+branch%3Amain)
@@ -23,7 +24,7 @@ A minimalist __RSpec clone__ with all the essentials.
23
24
 
24
25
  * There is no option to activate monkey-patching.
25
26
  * It does not rely on [hacks such as `at_exit` hook](https://blog.arkency.com/2013/06/are-we-abusing-at-exit/) to trigger the tests.
26
- * Built-in matchers [do not trust _actual_](https://asciinema.org/a/29172?autoplay=1&speed=2) and do not send it messages.
27
+ * Malicious _actual values_ cannot [hack results](https://asciinema.org/a/423547?autoplay=1&speed=2).
27
28
  * If no `subject` has been explicitly determined, none is defined.
28
29
  * If no described class is set, `described_class` is undefined instead of `nil`.
29
30
  * Expectations cannot be added inside a `before` block.
@@ -67,7 +68,7 @@ A basic spec looks something like this:
67
68
 
68
69
  ### Anatomy of a spec file
69
70
 
70
- To use the `RSpec` module and its DSL, you need to add `require "r_spec/clone"` to your spec files.
71
+ To use the `RSpec` module and its DSL, you need to add `require "r_spec"` to your spec files.
71
72
  Many projects use a custom spec helper which organizes these includes.
72
73
 
73
74
  Concrete test cases are defined in `it` blocks.
@@ -95,12 +96,28 @@ For unit tests, it is recommended to follow the conventions for method names:
95
96
 
96
97
  To establish certain contexts — think _empty array_ versus _array with elements_ — the `context` method may be used to communicate this to the reader.
97
98
 
98
- Finally, each block of code can be run in a subprocess to isolate side effects with the equivalent methods:
99
+ To execute unit tests while isolating side effects in a sub-process, declined methods can be used: `describe!`, `context!`, `it!`, `its!`. Here is an example:
99
100
 
100
- * `describe!`
101
- * `context!`
102
- * `it!`
103
- * `its!`
101
+ ```ruby
102
+ app = "foo"
103
+
104
+ RSpec.describe "Side effects per example" do
105
+ it! "runs the example in isolation" do
106
+ expect { app.gsub!("foo", "bar") }.to eq "bar"
107
+ expect(app).to eq "bar"
108
+ end
109
+
110
+ it "runs the example" do
111
+ expect(app).to eq "foo"
112
+ end
113
+ end
114
+
115
+ # Success: expected to eq "bar".
116
+ # Success: expected to eq "bar".
117
+ # Success: expected to eq "foo".
118
+ ```
119
+
120
+ Note: if you are wondering what the Ruby code generated by using the DSL might look like, an article presents the correspondence between each method via simple examples, available in [English](https://dev.to/cyri_/what-ruby-code-to-expect-from-a-testing-dsl-4oe1), [Chinese](https://ruby-china.org/topics/41441) and [Japanese](https://qiita.com/cyril/items/17ee758e162bae144a07).
104
121
 
105
122
  ### Expectations
106
123
 
@@ -221,13 +238,13 @@ bundle exec rake
221
238
 
222
239
  Benchmark against [100 executions of a file containing 1 expectation](https://github.com/cyril/r_spec-clone.rb/blob/main/benchmark/boot_time/) (lower is better).
223
240
 
224
- ![Boot time](https://r-spec.dev/benchmark-boot-time.svg)
241
+ ![Boot time benchmark](https://r-spec.dev/benchmark-boot-time.svg)
225
242
 
226
- ### Run time
243
+ ### Runtime
227
244
 
228
245
  Benchmark against [1 execution of a file containing 1,000,000 expectations](https://github.com/cyril/r_spec-clone.rb/blob/main/benchmark/run_time/) (lower is better).
229
246
 
230
- ![Run time](https://r-spec.dev/benchmark-run-time.svg)
247
+ ![Runtime benchmark](https://r-spec.dev/benchmark-run-time.svg)
231
248
 
232
249
  ## Test suite
233
250
 
@@ -237,6 +254,7 @@ __RSpec clone__'s specifications are self-described here: [spec/](https://github
237
254
 
238
255
  * Home page: [https://r-spec.dev/](https://r-spec.dev/)
239
256
  * Cheatsheet: [https://r-spec.dev/cheatsheet.html](https://r-spec.dev/cheatsheet.html)
257
+ * Blog post: [https://batman.buzz/introducing-a-new-rspec-850d48c0f901](https://batman.buzz/introducing-a-new-rspec-850d48c0f901)
240
258
  * Source code: [https://github.com/cyril/r_spec-clone.rb](https://github.com/cyril/r_spec-clone.rb)
241
259
  * API Doc: [https://rubydoc.info/gems/r_spec-clone](https://rubydoc.info/gems/r_spec-clone)
242
260
  * Twitter: [https://twitter.com/cyri\_](https://twitter.com/cyri\_)
data/lib/r_spec.rb CHANGED
@@ -5,7 +5,7 @@ require_relative File.join("r_spec", "clone", "dsl")
5
5
  # Top level namespace for the RSpec clone.
6
6
  #
7
7
  # @example The true from the false
8
- # require "r_spec/clone"
8
+ # require "r_spec"
9
9
  #
10
10
  # RSpec.describe "The true from the false" do
11
11
  # it { expect(false).not_to be true }
@@ -15,7 +15,7 @@ require_relative File.join("r_spec", "clone", "dsl")
15
15
  # # Success: expected false not to be true.
16
16
  #
17
17
  # @example The basic behavior of arrays
18
- # require "r_spec/clone"
18
+ # require "r_spec"
19
19
  #
20
20
  # RSpec.describe Array do
21
21
  # describe "#size" do
@@ -41,7 +41,7 @@ require_relative File.join("r_spec", "clone", "dsl")
41
41
  # # Success: expected false to be false.
42
42
  #
43
43
  # @example An inherited definition of let
44
- # require "r_spec/clone"
44
+ # require "r_spec"
45
45
  #
46
46
  # RSpec.describe Integer do
47
47
  # let(:answer) { 42 }
@@ -69,7 +69,7 @@ module RSpec
69
69
  # array_ versus _array with elements_.
70
70
  #
71
71
  # @example
72
- # require "r_spec/clone"
72
+ # require "r_spec"
73
73
  #
74
74
  # RSpec.context "when divided by zero" do
75
75
  # subject { 42 / 0 }
@@ -83,30 +83,62 @@ module RSpec
83
83
  # @param description [String] A description that usually begins with "when",
84
84
  # "with" or "without".
85
85
  # @param block [Proc] The block to define the specs.
86
- #
87
- # @api public
88
86
  def self.context(description, &block)
89
87
  Clone::Dsl.context(description, &block)
90
88
  end
91
89
 
92
90
  # :nocov:
93
- #
91
+
94
92
  # Runs a context example group in a subprocess to isolate side effects.
95
93
  #
94
+ # @example
95
+ # str = "Hello, world!"
96
+ #
97
+ # require "r_spec"
98
+ #
99
+ # RSpec.context! "when a string becomes uppercase" do
100
+ # before do
101
+ # str.upcase!
102
+ # end
103
+ #
104
+ # it { expect(str).to eq "HELLO, WORLD!" }
105
+ # end
106
+ #
107
+ # # Output to the console
108
+ # # Success: expected to eq "HELLO, WORLD!".
109
+ #
110
+ # RSpec.it { expect(str).to eq "Hello, world!" }
111
+ #
112
+ # # Output to the console
113
+ # # Success: expected to eq "Hello, world!".
114
+ #
96
115
  # @param (see #context)
97
116
  def self.context!(description, &block)
98
117
  Clone::Dsl.context!(description, &block)
99
118
  end
119
+
100
120
  # :nocov:
101
121
 
102
122
  # Defines an example group that describes a unit to be tested.
103
123
  #
104
124
  # @example
105
- # require "r_spec/clone"
125
+ # require "r_spec"
126
+ #
127
+ # RSpec.describe String do
128
+ # it { expect(described_class).to be String }
129
+ # end
130
+ #
131
+ # # Output to the console
132
+ # # Success: expected to be String.
133
+ #
134
+ # @example
135
+ # require "r_spec"
106
136
  #
107
137
  # RSpec.describe String do
138
+ # let(:foo) { "foo" }
139
+ #
108
140
  # describe "+" do
109
- # it("concats") { expect("foo" + "bar").to eq "foobar" }
141
+ # it("concats") { expect(foo + "bar").to eq "foobar" }
110
142
  # end
111
143
  # end
112
144
  #
@@ -115,20 +147,40 @@ module RSpec
115
147
  #
116
148
  # @param const [Module, String] A module to include in block context.
117
149
  # @param block [Proc] The block to define the specs.
118
- #
119
- # @api public
120
150
  def self.describe(const, &block)
121
151
  Clone::Dsl.describe(const, &block)
122
152
  end
123
153
 
124
154
  # :nocov:
125
- #
155
+
126
156
  # Runs a describe example group in a subprocess to isolate side effects.
127
157
  #
158
+ # @example
159
+ # $app = "foo"
160
+ #
161
+ # require "r_spec"
162
+ #
163
+ # RSpec.describe! "#gsub!" do
164
+ # before do
165
+ # $app.gsub!("o", "0")
166
+ # end
167
+ #
168
+ # it { expect($app).to eq "f00" }
169
+ # end
170
+ #
171
+ # # Output to the console
172
+ # # Success: expected to eq "f00".
173
+ #
174
+ # RSpec.it { expect($app).to eq "foo" }
175
+ #
176
+ # # Output to the console
177
+ # # Success: expected to eq "foo".
178
+ #
128
179
  # @param (see #describe)
129
180
  def self.describe!(const, &block)
130
181
  Clone::Dsl.describe!(const, &block)
131
182
  end
183
+
132
184
  # :nocov:
133
185
 
134
186
  # Defines a concrete test case.
@@ -136,7 +188,7 @@ module RSpec
136
188
  # The test is performed by the block supplied to &block.
137
189
  #
138
190
  # @example The integer after 41
139
- # require "r_spec/clone"
191
+ # require "r_spec"
140
192
  #
141
193
  # RSpec.it { expect(41.next).to be 42 }
142
194
  #
@@ -149,21 +201,21 @@ module RSpec
149
201
  # @param name [String, nil] The name of the spec.
150
202
  # @param block [Proc] An expectation to evaluate.
151
203
  #
152
- # @raise (see RSpec::ExpectationTarget::Base#result)
153
- # @return (see RSpec::ExpectationTarget::Base#result)
154
- #
155
- # @api public
204
+ # @raise (see RSpec::Clone::ExpectationTarget::Base#result)
205
+ # @return (see RSpec::Clone::ExpectationTarget::Base#result)
156
206
  def self.it(name = nil, &block)
157
207
  Clone::Dsl.it(name, &block)
158
208
  end
159
209
 
160
210
  # :nocov:
161
- #
211
+
162
212
  # Runs a concrete test case in a subprocess to isolate side effects.
163
213
  #
164
214
  # @example
165
215
  # app = "Hello, world!"
166
216
  #
217
+ # require "r_spec"
218
+ #
167
219
  # RSpec.it! { expect(app.gsub!("world", "Alice")).to eq "Hello, Alice!" }
168
220
  #
169
221
  # # Output to the console
@@ -181,6 +233,7 @@ module RSpec
181
233
  def self.it!(name = nil, &block)
182
234
  Clone::Dsl.it!(name, &block)
183
235
  end
236
+
184
237
  # :nocov:
185
238
 
186
239
  # Defines a pending test case.
@@ -189,7 +242,7 @@ module RSpec
189
242
  # not yet implemented.
190
243
  #
191
244
  # @example
192
- # require "r_spec/clone"
245
+ # require "r_spec"
193
246
  #
194
247
  # RSpec.pending "is implemented but waiting" do
195
248
  # expect something to be finished
@@ -207,8 +260,6 @@ module RSpec
207
260
  # @param message [String] The reason why the example is pending.
208
261
  #
209
262
  # @return [nil] Write a message to STDOUT.
210
- #
211
- # @api public
212
263
  def self.pending(message)
213
264
  Clone::Dsl.pending(message)
214
265
  end
@@ -3,8 +3,6 @@
3
3
  module RSpec
4
4
  module Clone
5
5
  # Send log messages to the console.
6
- #
7
- # @api private
8
6
  module Console
9
7
  # @param report [::Expresenter::Pass] Passed expectation result presenter.
10
8
  #
@@ -16,7 +16,7 @@ module RSpec
16
16
  # Executes the given block before each spec in the current context runs.
17
17
  #
18
18
  # @example
19
- # require "r_spec/clone"
19
+ # require "r_spec"
20
20
  #
21
21
  # RSpec.describe Integer do
22
22
  # before do
@@ -42,6 +42,8 @@ module RSpec
42
42
  # # Success: expected to be 123.
43
43
  #
44
44
  # @param block [Proc] The content to execute at the class initialization.
45
+ #
46
+ # @api public
45
47
  def self.before(&block)
46
48
  define_method(BEFORE_METHOD) do
47
49
  super()
@@ -54,7 +56,7 @@ module RSpec
54
56
  # Executes the given block after each spec in the current context runs.
55
57
  #
56
58
  # @example
57
- # require "r_spec/clone"
59
+ # require "r_spec"
58
60
  #
59
61
  # RSpec.describe Integer do
60
62
  # after do
@@ -69,6 +71,8 @@ module RSpec
69
71
  # # That is the answer to everything.
70
72
  #
71
73
  # @param block [Proc] The content to execute at the class initialization.
74
+ #
75
+ # @api public
72
76
  def self.after(&block)
73
77
  define_method(AFTER_METHOD) do
74
78
  instance_exec(&block)
@@ -81,7 +85,7 @@ module RSpec
81
85
  # Sets a user-defined property.
82
86
  #
83
87
  # @example
84
- # require "r_spec/clone"
88
+ # require "r_spec"
85
89
  #
86
90
  # RSpec.describe "Name stories" do
87
91
  # let(:name) { "Bob" }
@@ -103,6 +107,8 @@ module RSpec
103
107
  # @param block [Proc] The content of the method to define.
104
108
  #
105
109
  # @return [Symbol] A private method that define the block content.
110
+ #
111
+ # @api public
106
112
  def self.let(name, *args, **kwargs, &block)
107
113
  raise Error::ReservedMethod if [BEFORE_METHOD, AFTER_METHOD].include?(name.to_sym)
108
114
 
@@ -112,7 +118,7 @@ module RSpec
112
118
  # Sets a user-defined property named {#subject}.
113
119
  #
114
120
  # @example
115
- # require "r_spec/clone"
121
+ # require "r_spec"
116
122
  #
117
123
  # RSpec.describe Array do
118
124
  # subject { [1, 2, 3] }
@@ -120,13 +126,23 @@ module RSpec
120
126
  # it "has the prescribed elements" do
121
127
  # expect(subject).to eq([1, 2, 3])
122
128
  # end
129
+ #
130
+ # it { is_expected.to be_an_instance_of described_class }
131
+ #
132
+ # its(:size) { is_expected.to be 3 }
133
+ # its(:downcase) { is_expected.to raise_exception NoMethodError }
123
134
  # end
124
135
  #
125
136
  # # Output to the console
126
137
  # # Success: expected to eq [1, 2, 3].
138
+ # # Success: expected [1, 2, 3] to be an instance of Array.
139
+ # # Success: expected to be 3.
140
+ # # Success: undefined method `downcase' for [1, 2, 3]:Array.
127
141
  #
128
142
  # @param block [Proc] The subject to set.
129
143
  # @return [Symbol] A {#subject} method that define the block content.
144
+ #
145
+ # @api public
130
146
  def self.subject(&block)
131
147
  let(__method__, &block)
132
148
  end
@@ -134,7 +150,7 @@ module RSpec
134
150
  # Defines an example group that describes a unit to be tested.
135
151
  #
136
152
  # @example
137
- # require "r_spec/clone"
153
+ # require "r_spec"
138
154
  #
139
155
  # RSpec.describe String do
140
156
  # describe "+" do
@@ -147,6 +163,8 @@ module RSpec
147
163
  #
148
164
  # @param const [Module, String] A module to include in block context.
149
165
  # @param block [Proc] The block to define the specs.
166
+ #
167
+ # @api public
150
168
  def self.describe(const, &block)
151
169
  desc = ::Class.new(self)
152
170
  desc.let(:described_class) { const } if const.is_a?(::Module)
@@ -154,20 +172,53 @@ module RSpec
154
172
  end
155
173
 
156
174
  # :nocov:
157
- #
175
+
158
176
  # Runs a describe example group in a subprocess to isolate side effects.
159
177
  #
178
+ # @example
179
+ # $app = "foo"
180
+ #
181
+ # require "r_spec"
182
+ #
183
+ # RSpec.describe "Scoped side effects" do
184
+ # describe! "#gsub!" do
185
+ # before do
186
+ # $app.gsub!("o", "0")
187
+ # end
188
+ #
189
+ # context! "when isolated in the context" do
190
+ # before do
191
+ # $app.gsub!("f", "F")
192
+ # end
193
+ #
194
+ # it { expect($app).to eq "F00" }
195
+ # end
196
+ #
197
+ # it { expect($app).to eq "f00" }
198
+ # end
199
+ #
200
+ # it { expect($app).to eq "foo" }
201
+ # end
202
+ #
203
+ # # Output to the console
204
+ # # Success: expected to eq "F00".
205
+ # # Success: expected to eq "f00".
206
+ # # Success: expected to eq "foo".
207
+ #
160
208
  # @param (see #describe)
209
+ #
210
+ # @api public
161
211
  def self.describe!(const, &block)
162
212
  fork! { describe(const, &block) }
163
213
  end
214
+
164
215
  # :nocov:
165
216
 
166
217
  # Defines an example group that establishes a specific context, like
167
218
  # _empty array_ versus _array with elements_.
168
219
  #
169
220
  # @example
170
- # require "r_spec/clone"
221
+ # require "r_spec"
171
222
  #
172
223
  # RSpec.describe "web resource" do
173
224
  # context "when resource is not found" do
@@ -186,19 +237,55 @@ module RSpec
186
237
  # @param _description [String] A description that usually begins with
187
238
  # "when", "with" or "without".
188
239
  # @param block [Proc] The block to define the specs.
240
+ #
241
+ # @api public
189
242
  def self.context(_description, &block)
190
243
  desc = ::Class.new(self)
191
244
  desc.instance_eval(&block)
192
245
  end
193
246
 
194
247
  # :nocov:
195
- #
248
+
196
249
  # Runs a context example group in a subprocess to isolate side effects.
197
250
  #
251
+ # @example
252
+ # app = "Hello, world!"
253
+ #
254
+ # require "r_spec"
255
+ #
256
+ # RSpec.describe String do
257
+ # subject do
258
+ # app
259
+ # end
260
+ #
261
+ # before do
262
+ # subject.gsub!("world", person)
263
+ # end
264
+ #
265
+ # context! "when Alice is greeted" do
266
+ # let(:person) { "Alice" }
267
+ #
268
+ # it { is_expected.to eq "Hello, Alice!" }
269
+ # end
270
+ #
271
+ # context! "when Bob is greeted" do
272
+ # let(:person) { "Bob" }
273
+ #
274
+ # it { is_expected.to eq "Hello, Bob!" }
275
+ # end
276
+ # end
277
+ #
278
+ # # Output to the console
279
+ # # Success: expected to eq "Hello, Alice!".
280
+ # # Success: expected to eq "Hello, Bob!".
281
+ #
198
282
  # @param (see #context)
283
+ #
284
+ # @api public
199
285
  def self.context!(description, &block)
200
286
  fork! { context(description, &block) }
201
287
  end
288
+
202
289
  # :nocov:
203
290
 
204
291
  # Defines a concrete test case.
@@ -206,7 +293,7 @@ module RSpec
206
293
  # The test is performed by the block supplied to `&block`.
207
294
  #
208
295
  # @example The integer after 41
209
- # require "r_spec/clone"
296
+ # require "r_spec"
210
297
  #
211
298
  # RSpec.describe Integer do
212
299
  # it { expect(41.next).to be 42 }
@@ -216,7 +303,7 @@ module RSpec
216
303
  # # Success: expected to be 42.
217
304
  #
218
305
  # @example A division by zero
219
- # require "r_spec/clone"
306
+ # require "r_spec"
220
307
  #
221
308
  # RSpec.describe Integer do
222
309
  # subject { 41 }
@@ -239,29 +326,54 @@ module RSpec
239
326
  #
240
327
  # @raise (see ExpectationTarget::Base#result)
241
328
  # @return (see ExpectationTarget::Base#result)
329
+ #
330
+ # @api public
242
331
  def self.it(_name = nil, &block)
243
- example = ::Class.new(self) { include ExpectationHelper::It }.new
244
- run(example, &block)
332
+ run(example_without_attribute.new, &block)
245
333
  end
246
334
 
247
335
  # :nocov:
248
- #
336
+
249
337
  # Runs a concrete test case in a subprocess to isolate side effects.
250
338
  #
339
+ # @example
340
+ # app = "foo"
341
+ #
342
+ # require "r_spec"
343
+ #
344
+ # RSpec.describe "Side effects per example" do
345
+ # it! "runs the example in isolation" do
346
+ # expect { app.gsub!("foo", "bar") }.to eq "bar"
347
+ # expect(app).to eq "bar"
348
+ # end
349
+ #
350
+ # it "runs the example" do
351
+ # expect(app).to eq "foo"
352
+ # end
353
+ # end
354
+ #
355
+ # # Output to the console
356
+ # # Success: expected to eq "bar".
357
+ # # Success: expected to eq "bar".
358
+ # # Success: expected to eq "foo".
359
+ #
251
360
  # @param (see #it)
252
361
  #
253
362
  # @raise (see ExpectationTarget::Base#result)
254
363
  # @return (see ExpectationTarget::Base#result)
364
+ #
365
+ # @api public
255
366
  def self.it!(name = nil, &block)
256
367
  fork! { it(name, &block) }
257
368
  end
369
+
258
370
  # :nocov:
259
371
 
260
372
  # Defines a single concrete test case that specifies the actual value of
261
373
  # an attribute of the subject using {ExpectationHelper::Its#is_expected}.
262
374
  #
263
375
  # @example The integer after 41
264
- # require "r_spec/clone"
376
+ # require "r_spec"
265
377
  #
266
378
  # RSpec.describe Integer do
267
379
  # subject { 41 }
@@ -273,7 +385,7 @@ module RSpec
273
385
  # # Success: expected to be 42.
274
386
  #
275
387
  # @example A division by zero
276
- # require "r_spec/clone"
388
+ # require "r_spec"
277
389
  #
278
390
  # RSpec.describe Integer do
279
391
  # subject { 41 }
@@ -285,10 +397,10 @@ module RSpec
285
397
  # # Success: divided by 0.
286
398
  #
287
399
  # @example A spec without subject
288
- # require "r_spec/clone"
400
+ # require "r_spec"
289
401
  #
290
402
  # RSpec.describe Integer do
291
- # its(:boom) { is_expected.to raise_exception RSpec::Clone::Error::UndefinedSubject }
403
+ # its(:abs) { is_expected.to raise_exception RSpec::Clone::Error::UndefinedSubject }
292
404
  # end
293
405
  #
294
406
  # # Output to the console
@@ -301,30 +413,48 @@ module RSpec
301
413
  #
302
414
  # @raise (see ExpectationTarget::Base#result)
303
415
  # @return (see ExpectationTarget::Base#result)
416
+ #
417
+ # @api public
304
418
  def self.its(attribute, *args, **kwargs, &block)
305
- example = ::Class.new(self) do
306
- include ExpectationHelper::Its
307
-
308
- define_method(:actual) do
309
- subject.public_send(attribute, *args, **kwargs)
310
- end
311
- end.new
312
-
313
- run(example, &block)
419
+ run(example_with_attribute(attribute, *args, **kwargs).new, &block)
314
420
  end
315
421
 
316
422
  # :nocov:
317
- #
423
+
318
424
  # Runs a single concrete test case in a subprocess to isolate side
319
425
  # effects.
320
426
  #
427
+ # @example
428
+ # app = "foo"
429
+ #
430
+ # require "r_spec"
431
+ #
432
+ # RSpec.describe "Isolated side effect" do
433
+ # subject do
434
+ # app
435
+ # end
436
+ #
437
+ # its!(:upcase) { is_expected.to eq "FOO" }
438
+ #
439
+ # it "tests the original value" do
440
+ # expect(app).to eq "foo"
441
+ # end
442
+ # end
443
+ #
444
+ # # Output to the console
445
+ # # Success: expected to eq "FOO".
446
+ # # Success: expected to eq "foo".
447
+ #
321
448
  # @param (see #it)
322
449
  #
323
450
  # @raise (see ExpectationTarget::Base#result)
324
451
  # @return (see ExpectationTarget::Base#result)
452
+ #
453
+ # @api public
325
454
  def self.its!(attribute, *args, **kwargs, &block)
326
455
  fork! { its(attribute, *args, **kwargs, &block) }
327
456
  end
457
+
328
458
  # :nocov:
329
459
 
330
460
  # Defines a pending test case.
@@ -333,7 +463,7 @@ module RSpec
333
463
  # is not yet implemented.
334
464
  #
335
465
  # @example
336
- # require "r_spec/clone"
466
+ # require "r_spec"
337
467
  #
338
468
  # RSpec.describe "an example" do
339
469
  # pending "is implemented but waiting" do
@@ -356,6 +486,25 @@ module RSpec
356
486
  Console.passed_spec Error::PendingExpectation.result(message)
357
487
  end
358
488
 
489
+ # Example class for concrete test case.
490
+ def self.example_without_attribute
491
+ ::Class.new(self) do
492
+ prepend ExpectationHelper::It
493
+ end
494
+ end
495
+
496
+ # Example class for concrete test case that specifies the actual value of
497
+ # an attribute of the subject.
498
+ def self.example_with_attribute(attribute, *args, **kwargs)
499
+ ::Class.new(self) do
500
+ prepend ExpectationHelper::Its
501
+
502
+ define_method(:actual) do
503
+ subject.public_send(attribute, *args, **kwargs)
504
+ end
505
+ end
506
+ end
507
+
359
508
  # Creates a subprocess and runs the block inside.
360
509
  def self.fork!(&block)
361
510
  pid = fork(&block)
@@ -375,15 +524,18 @@ module RSpec
375
524
  example&.send(AFTER_METHOD)
376
525
  end
377
526
 
378
- private_class_method :fork!, :run
527
+ private_class_method :example_without_attribute, :example_with_attribute, :fork!, :run
379
528
 
380
529
  private
381
530
 
531
+ # If the first argument to a {.describe} definition is a class (or a
532
+ # module), this method will be overridden to return it.
382
533
  def described_class
383
534
  raise Error::UndefinedDescribedClass,
384
535
  "the first argument to at least one example group must be a module"
385
536
  end
386
537
 
538
+ # If a subject is defined, this method will be overridden to return it.
387
539
  def subject
388
540
  raise Error::UndefinedSubject, "subject not explicitly defined"
389
541
  end
@@ -8,8 +8,6 @@ require_relative File.join("error", "undefined_subject")
8
8
  module RSpec
9
9
  module Clone
10
10
  # Namespace for exceptions.
11
- #
12
- # @api private
13
11
  module Error
14
12
  end
15
13
  end
@@ -6,8 +6,6 @@ module RSpec
6
6
  module Clone
7
7
  module Error
8
8
  # Exception for pending expectations.
9
- #
10
- # @api private
11
9
  class PendingExpectation < ::RuntimeError
12
10
  # @param message [String] The not implemented expectation description.
13
11
  #
@@ -4,8 +4,6 @@ module RSpec
4
4
  module Clone
5
5
  module Error
6
6
  # Exception for reserved methods.
7
- #
8
- # @api private
9
7
  class ReservedMethod < ::RuntimeError
10
8
  end
11
9
  end
@@ -4,8 +4,6 @@ module RSpec
4
4
  module Clone
5
5
  module Error
6
6
  # Exception for undefined described classes.
7
- #
8
- # @api private
9
7
  class UndefinedDescribedClass < ::RuntimeError
10
8
  end
11
9
  end
@@ -4,8 +4,6 @@ module RSpec
4
4
  module Clone
5
5
  module Error
6
6
  # Exception for undefined subjects.
7
- #
8
- # @api private
9
7
  class UndefinedSubject < ::RuntimeError
10
8
  end
11
9
  end
@@ -18,8 +18,8 @@ module RSpec
18
18
  # @return [Block, Value] The wrapped target of an expectation.
19
19
  #
20
20
  # @example
21
- # expect("foo") # => #<RSpec::ExpectationTarget::Value:0x00007fb6b823 @actual="foo">
22
- # expect { Boom } # => #<RSpec::ExpectationTarget::Block:0x00007fb6b826 @callable=#<Proc:0x00007fb6b826>>
21
+ # expect("foo") # => #<RSpec::Clone::ExpectationTarget::Value:0x00007f @actual="foo">
22
+ # expect { Boom } # => #<RSpec::Clone::ExpectationTarget::Block:0x00007f @callable=#<Proc:0x00007f>>
23
23
  #
24
24
  # @api public
25
25
  def expect(value = self.class.superclass, &block)
@@ -31,7 +31,7 @@ module RSpec
31
31
  # @return [Block] The wrapped target of an expectation.
32
32
  #
33
33
  # @example
34
- # is_expected # => #<RSpec::ExpectationTarget::Block:0x00007fb6b8263df8 @callable=#<Proc:0x00007fb6b8263e20>>
34
+ # is_expected # => #<RSpec::Clone::ExpectationTarget::Block:0x00007fb6b8 @callable=#<Proc:0x00007fb6b8>>
35
35
  #
36
36
  # @api public
37
37
  def is_expected
@@ -15,7 +15,7 @@ module RSpec
15
15
  # @return [Block] The wrapped target of an expectation.
16
16
  #
17
17
  # @example
18
- # is_expected # => #<RSpec::ExpectationTarget::Block:0x00007fb6b8263df8 @callable=#<Proc:0x00007fb6b8263e20>>
18
+ # is_expected # => #<RSpec::Clone::ExpectationTarget::Block:0x00007f @callable=#<Proc:0x00007f>>
19
19
  #
20
20
  # @api public
21
21
  def is_expected
@@ -1,16 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "matchi/rspec"
4
-
5
- require_relative File.join("..", "error", "pending_expectation")
4
+ require "matchi/helper"
6
5
 
7
6
  module RSpec
8
7
  module Clone
9
8
  module ExpectationHelper
10
9
  # Abstract expectation helper base module.
11
10
  #
12
- # This module defines a number of methods to create expectations, which are
13
- # automatically included into examples.
11
+ # This module defines a number of methods to create expectations, which
12
+ # are automatically included into examples.
14
13
  #
15
14
  # It also includes a collection of expectation matchers 🤹
16
15
  #
@@ -6,12 +6,10 @@ require_relative File.join("expectation_target", "value")
6
6
  module RSpec
7
7
  module Clone
8
8
  # Wraps the target of an expectation.
9
- #
10
- # @api private
11
9
  module ExpectationTarget
12
10
  # @param undefined_value A sentinel value to be able to tell when the user
13
- # did not pass an argument. We can't use `nil` for that because `nil` is a
14
- # valid value to pass.
11
+ # did not pass an argument. We can't use `nil` for that because `nil` is
12
+ # a valid value to pass.
15
13
  # @param value [#object_id, nil] An actual value.
16
14
  # @param block [#call, nil] A code to evaluate.
17
15
  #
@@ -10,14 +10,14 @@ module RSpec
10
10
  module ExpectationTarget
11
11
  # Abstract expectation target base class.
12
12
  #
13
- # @note `RSpec::ExpectationTarget::Base` is not intended to be instantiated
14
- # directly by users. Use `expect` instead.
13
+ # @note `RSpec::Clone::ExpectationTarget::Base` is not intended to be
14
+ # instantiated directly by users. Use `expect` instead.
15
15
  class Base
16
16
  # Instantiate a new expectation target.
17
17
  #
18
- # @param actual [#object_id] The actual value of the code to evaluate.
19
- def initialize(actual)
20
- @actual = actual
18
+ # @param input [#object_id, Proc] The code to evaluate.
19
+ def initialize(input)
20
+ @input = input
21
21
  end
22
22
 
23
23
  # Runs the given expectation, passing if `matcher` returns true.
@@ -52,6 +52,36 @@ module RSpec
52
52
 
53
53
  protected
54
54
 
55
+ # @param test [::TestTube::Base] The state of the experiment.
56
+ # @param matcher [#matches?] The matcher.
57
+ # @param negate [Boolean] The assertion is positive or negative.
58
+ #
59
+ # @return [nil] Write a message to STDOUT.
60
+ #
61
+ # @raise [SystemExit] Terminate execution immediately by calling
62
+ # `Kernel.exit(false)` with a failure message written to STDERR.
63
+ def absolute_requirement(test, matcher:, negate:)
64
+ result(
65
+ passed?(test),
66
+ actual: test.actual,
67
+ error: test.error,
68
+ got: test.got,
69
+ matcher: matcher,
70
+ negate: negate
71
+ )
72
+ end
73
+
74
+ # Code experiment result.
75
+ #
76
+ # @param test [::TestTube::Base] The state of the experiment.
77
+ #
78
+ # @see https://github.com/fixrb/test_tube
79
+ #
80
+ # @return [Boolean] The result of the test (passed or failed).
81
+ def passed?(test)
82
+ test.got.equal?(true)
83
+ end
84
+
55
85
  # @param passed [Boolean] The high expectation passed or failed.
56
86
  # @param actual [#object_id] The actual value.
57
87
  # @param error [Exception, nil] Any raised exception.
@@ -63,8 +93,6 @@ module RSpec
63
93
  #
64
94
  # @raise [SystemExit] Terminate execution immediately by calling
65
95
  # `Kernel.exit(false)` with a failure message written to STDERR.
66
- #
67
- # @api private
68
96
  def result(passed, actual:, error:, got:, matcher:, negate:)
69
97
  Console.passed_spec ::Expresenter.call(passed).with(
70
98
  actual: actual,
@@ -16,31 +16,20 @@ module RSpec
16
16
  # # with `not_to`
17
17
  # expect { actual }.not_to be(4)
18
18
  #
19
- # @note `RSpec::ExpectationTarget::Block` is not intended to be instantiated
20
- # directly by users. Use `expect` instead.
19
+ # @note `RSpec::Clone::ExpectationTarget::Block` is not intended to be
20
+ # instantiated directly by users. Use `expect` instead.
21
21
  class Block < Base
22
22
  protected
23
23
 
24
24
  # @param matcher [#matches?] The matcher.
25
25
  # @param negate [Boolean] The assertion is positive or negative.
26
26
  #
27
- # @return [nil] Write a message to STDOUT.
27
+ # @return (see Base#absolute_requirement)
28
28
  #
29
- # @raise [SystemExit] Terminate execution immediately by calling
30
- # `Kernel.exit(false)` with a failure message written to STDERR.
29
+ # @raise (see Base#absolute_requirement)
31
30
  def absolute_requirement(matcher:, negate:)
32
- experiment = ::TestTube.invoke(
33
- @actual,
34
- isolation: false,
35
- matcher: matcher,
36
- negate: negate
37
- )
38
-
39
- result(
40
- experiment.got.equal?(true),
41
- actual: experiment.actual,
42
- error: experiment.error,
43
- got: experiment.got,
31
+ super(
32
+ ::TestTube.invoke(isolate: false, matcher: matcher, negate: negate, &@input),
44
33
  matcher: matcher,
45
34
  negate: negate
46
35
  )
@@ -16,30 +16,20 @@ module RSpec
16
16
  # # with `not_to`
17
17
  # expect(actual).not_to be(4)
18
18
  #
19
- # @note `RSpec::ExpectationTarget::Value` is not intended to be instantiated
20
- # directly by users. Use `expect` instead.
19
+ # @note `RSpec::Clone::ExpectationTarget::Value` is not intended to be
20
+ # instantiated directly by users. Use `expect` instead.
21
21
  class Value < Base
22
22
  protected
23
23
 
24
24
  # @param matcher [#matches?] The matcher.
25
25
  # @param negate [Boolean] The assertion is positive or negative.
26
26
  #
27
- # @return [nil] Write a message to STDOUT.
27
+ # @return (see Base#absolute_requirement)
28
28
  #
29
- # @raise [SystemExit] Terminate execution immediately by calling
30
- # `Kernel.exit(false)` with a failure message written to STDERR.
29
+ # @raise (see Base#absolute_requirement)
31
30
  def absolute_requirement(matcher:, negate:)
32
- experiment = ::TestTube.pass(
33
- @actual,
34
- matcher: matcher,
35
- negate: negate
36
- )
37
-
38
- result(
39
- experiment.got.equal?(true),
40
- actual: experiment.actual,
41
- error: experiment.error,
42
- got: experiment.got,
31
+ super(
32
+ ::TestTube.pass(@input, matcher: matcher, negate: negate),
43
33
  matcher: matcher,
44
34
  negate: negate
45
35
  )
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: r_spec-clone
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-01 00:00:00.000000000 Z
11
+ date: 2021-07-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: expresenter
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.1.2
33
+ version: 1.2.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.1.2
40
+ version: 1.2.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: test_tube
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 1.1.0
47
+ version: 2.1.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 1.1.0
54
+ version: 2.1.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement