r_spec 1.0.0.beta4 → 1.0.0.beta9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d94ad6d7ba01dc04c4bcbe727543524f6a3d83cf69549a34453c1eefc5282e9
4
- data.tar.gz: 61b3ebe1299e69d7ef1f2c180325e85e1bda05e2347176ae507fc7430cf311ca
3
+ metadata.gz: 6599635e0de35bec4d9bae9f58ab1417a364eb2a0bd64db49b523be4d9ab1d16
4
+ data.tar.gz: 658671a666d8173ce075558dc7dc0f5604e2a98f86e0448485c2ad713ec518be
5
5
  SHA512:
6
- metadata.gz: 5d44ce99f58c17711be4b3f76cdfaf3c9923d031aa9a7d214db50b990825673b9bb485ce095195594fbeb45977763dc5a0be31770f089cd17dcb1e3ef1d6885b
7
- data.tar.gz: ca64a27f7cf4032b6b073b9fc2eb05aa37654c02a58289c41094e9afa000deccc3886d03d2043642a02e38cf10b6cc0fbd775e7ef9bc1c29cb592b813dcf5d04
6
+ metadata.gz: 45babe5acbed804ce1a61476c39028cdaa46256c343b0b6001b83e0bd5cda35a83770bd06776c7b76d22cb0dd3cbc07be7978d30d99c04cc1585e9cdeb933778
7
+ data.tar.gz: 78f72312fc8c2ad764647a1e6698f4a094bfc3129c0b2911f809502ba9a11d9a0da88ffafc2420c68d55fdaeeb8062258e8ff738013bded1bcf1970f536f192d
data/README.md CHANGED
@@ -2,18 +2,18 @@
2
2
 
3
3
  A minimalist __[RSpec](https://github.com/rspec/rspec) clone__ with all the essentials.
4
4
 
5
- ![What did you RSpec?](https://github.com/cyril/r_spec.rb/raw/main/img/what-did-you-rspec.svg)
5
+ ![What did you RSpec?](https://github.com/cyril/r_spec.rb/raw/main/img/what-did-you-rspec.jpg)
6
6
 
7
7
  ## Status
8
8
 
9
9
  [![Gem Version](https://badge.fury.io/rb/r_spec.svg)](https://badge.fury.io/rb/r_spec)
10
10
  [![Build Status](https://travis-ci.org/cyril/r_spec.rb.svg?branch=main)](https://travis-ci.org/cyril/r_spec.rb)
11
- [![Inline Docs](https://inch-ci.org/github/cyril/r_spec.rb.svg)](https://inch-ci.org/github/cyril/r_spec.rb)
12
11
  [![Documentation](https://img.shields.io/:yard-docs-38c800.svg)](https://rubydoc.info/gems/r_spec/frames)
13
12
 
14
- ## Goal
13
+ ## Project goals
15
14
 
16
- This clone attempts to provide most of RSpec's DSL to express expected outcomes of a code example without magic power.
15
+ * Enforce the guidelines and best practices outlined in the community [RSpec style guide](https://rspec.rubystyle.guide/).
16
+ * Provide most of RSpec's DSL to express expected outcomes of a code example without magic power.
17
17
 
18
18
  ## Some differences
19
19
 
@@ -22,11 +22,13 @@ This clone attempts to provide most of RSpec's DSL to express expected outcomes
22
22
  * There is no option to activate monkey-patching.
23
23
  * It does not rely on hacks such as `at_exit` hook to trigger the tests.
24
24
  * Built-in matchers do not trust _actual_ and do not send it messages.
25
- * The `subject` must be explicitly defined, otherwise it is not implemented.
25
+ * If no `subject` has been explicitly determined, none is defined.
26
+ * If no described class is set, `described_class` is undefined instead of `nil`.
26
27
  * Expectations cannot be added inside a `before` block.
27
28
  * [Arbitrary helper methods](https://relishapp.com/rspec/rspec-core/v/3-10/docs/helper-methods/arbitrary-helper-methods) are not exposed to examples.
28
29
  * The `let` method defines a helper method rather than a memoized helper method.
29
30
  * The one-liner `is_expected` syntax also works with block expectations.
31
+ * `subject`, `before`, `after` and `let` definitions must come before examples.
30
32
 
31
33
  ## Important ⚠️
32
34
 
@@ -45,7 +47,7 @@ Following [RubyGems naming conventions](https://guides.rubygems.org/name-your-ge
45
47
  Add this line to your application's Gemfile:
46
48
 
47
49
  ```ruby
48
- gem "r_spec", ">= 1.0.0.beta4"
50
+ gem "r_spec", ">= 1.0.0.beta9"
49
51
  ```
50
52
 
51
53
  And then execute:
@@ -80,16 +82,15 @@ Many projects use a custom spec helper which organizes these includes.
80
82
  Concrete test cases are defined in `it` blocks.
81
83
  An optional descriptive string states it's purpose and a block contains the main logic performing the test.
82
84
 
85
+ Test cases that have been defined or outlined but are not yet expected to work can be defined using `pending` instead of `it`. They will not be run but show up in the spec report as pending.
86
+
83
87
  An `it` block contains an example that should invoke the code to be tested and define what is expected of it.
84
88
  Each example can contain multiple expectations, but it should test only one specific behaviour.
85
89
 
86
- To express an expectation, wrap an object or block in `expect`, call `to` or `not_to` and pass it a matcher object.
87
-
90
+ To express an expectation, wrap an object or block in `expect`, call `to` (or `not_to`) and pass it a matcher object.
88
91
  If the expectation is met, code execution continues.
89
92
  Otherwise the example has _failed_ and other code will not be executed.
90
93
 
91
- Test cases that have been defined or outlined but are not yet expected to work can be defined using `pending` instead of `expect`. They will not be run but show up in the spec report as pending.
92
-
93
94
  In test files, specs are structured by example groups which are defined by `describe` and `context` sections.
94
95
  Typically a top level `describe` defines the outer unit (such as a class) to be tested by the spec.
95
96
  Further `describe` sections can be nested within the outer unit to specify smaller units under test (such as individual methods).
@@ -99,7 +100,7 @@ For unit tests, it is recommended to follow the conventions for method names:
99
100
  * outer `describe` is the name of the class, inner `describe` targets methods;
100
101
  * instance methods are prefixed with `#`, class methods with `.`.
101
102
 
102
- To establish certain contexts - think _empty array_ versus _array with elements_ - the `context` method may be used to communicate this to the reader.
103
+ To establish certain contexts think _empty array_ versus _array with elements_ the `context` method may be used to communicate this to the reader.
103
104
  It has a different name, but behaves exactly like `describe`.
104
105
 
105
106
  `describe` and `context` take an optional description as argument and a block containing the individual specs or nested groupings.
@@ -213,6 +214,19 @@ task spec: :test
213
214
  task default: :test
214
215
  ```
215
216
 
217
+ ## Performance
218
+
219
+ ### Runtime
220
+
221
+ Benchmark against [100 executions of a file containing one expectation](https://github.com/cyril/r_spec.rb/blob/main/benchmark/) (lower is better).
222
+
223
+ | Framework | Seconds to complete |
224
+ |-------------|---------------------|
225
+ | `r_spec` | 13.0 |
226
+ | `rspec` | 32.2 |
227
+ | `minitest` | 17.5 |
228
+ | `test-unit` | 20.5 |
229
+
216
230
  ## Test suite
217
231
 
218
232
  __RSpec clone__'s specifications are self-described here: [spec/](https://github.com/cyril/r_spec.rb/blob/main/spec/)
@@ -223,16 +237,22 @@ __RSpec clone__'s specifications are self-described here: [spec/](https://github
223
237
  * Source code: https://github.com/cyril/r_spec.rb
224
238
  * Twitter: [https://twitter.com/cyri\_](https://twitter.com/cyri\_)
225
239
 
226
- ## Versioning
227
-
228
- __RSpec clone__ follows [Semantic Versioning 2.0](https://semver.org/).
229
-
230
- ## Special thanks
240
+ ## Special thanks ❤️
231
241
 
232
242
  I would like to thank the whole [RSpec team](https://rspec.info/about/) for all their work.
233
243
  It's a great framework and it's a pleasure to work with every day.
234
244
 
235
- Without RSpec, this clone would not have been possible. ❤️
245
+ Without RSpec, this clone would not have been possible.
246
+
247
+ ## Buy me a coffee ☕
248
+
249
+ If you like this project please consider making a small donation.
250
+
251
+ [![Donate with Ethereum](https://github.com/cyril/r_spec.rb/raw/main/img/donate-eth.svg)](https://etherscan.io/address/0x834b5c1feaff5aebf9cd0f25dc38e741d65ab773)
252
+
253
+ ## Versioning
254
+
255
+ __RSpec clone__ follows [Semantic Versioning 2.0](https://semver.org/).
236
256
 
237
257
  ## License
238
258
 
data/lib/r_spec.rb CHANGED
@@ -1,14 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative File.join("r_spec", "dsl")
4
+
3
5
  # Top level namespace for the RSpec clone.
4
6
  #
5
7
  # @example The true from the false
6
8
  # require "r_spec"
7
9
  #
8
- # RSpec.describe do
10
+ # RSpec.describe "The true from the false" do
9
11
  # it { expect(false).not_to be true }
10
12
  # end
11
13
  #
14
+ # # Output to the console
15
+ # # Success: expected false not to be true.
16
+ #
12
17
  # @example The basic behavior of arrays
13
18
  # require "r_spec"
14
19
  #
@@ -30,6 +35,11 @@
30
35
  # end
31
36
  # end
32
37
  #
38
+ # # Output to the console
39
+ # # Success: expected to eq 3.
40
+ # # Success: expected true to be true.
41
+ # # Success: expected false to be false.
42
+ #
33
43
  # @example An inherited definition of let
34
44
  # require "r_spec"
35
45
  #
@@ -49,6 +59,10 @@
49
59
  # end
50
60
  # end
51
61
  #
62
+ # # Output to the console
63
+ # # Success: expected to be 42.
64
+ # # Success: expected to be 43.
65
+ #
52
66
  # @api public
53
67
  module RSpec
54
68
  # Specs are built with this method.
@@ -57,9 +71,7 @@ module RSpec
57
71
  # @param block [Proc] The block to define the specs.
58
72
  #
59
73
  # @api public
60
- def self.describe(const = nil, &block)
74
+ def self.describe(const, &block)
61
75
  Dsl.describe(const, &block)
62
76
  end
63
77
  end
64
-
65
- require_relative File.join("r_spec", "dsl")
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ # Send log messages to the console.
5
+ #
6
+ # @api private
7
+ module Console
8
+ # @param report [::Expresenter::Pass] Passed expectation result presenter.
9
+ #
10
+ # @see https://github.com/fixrb/expresenter
11
+ #
12
+ # @return [nil] Add a colored message to `$stdout`.
13
+ def self.passed_spec(report)
14
+ puts report.colored_string
15
+ end
16
+
17
+ # @param report [::Expresenter::Fail] Failed expectation result presenter.
18
+ #
19
+ # @see https://github.com/fixrb/expresenter
20
+ #
21
+ # @raise [SystemExit] Terminate execution immediately with colored message.
22
+ def self.failed_spec(report)
23
+ abort report.colored_string
24
+ end
25
+
26
+ # The Ruby source filename and line number containing this method or nil if
27
+ # this method was not defined in Ruby (i.e. native).
28
+ #
29
+ # @param filename [String, nil] The Ruby source filename.
30
+ # @param line [Integer, nil] The Ruby source line number.
31
+ #
32
+ # @return [String] The Ruby source filename and line number associated with
33
+ # the evaluated spec.
34
+ def self.source(filename, line)
35
+ puts [filename, line].compact.join(":")
36
+ end
37
+ end
38
+ end
data/lib/r_spec/dsl.rb CHANGED
@@ -1,78 +1,243 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "securerandom"
3
+ require_relative "console"
4
+ require_relative "error"
5
+ require_relative "expectation_helper"
4
6
 
5
7
  module RSpec
6
8
  # Abstract class for handling the domain-specific language.
7
9
  class Dsl
8
- # Instructs the spec runner to execute the given block before each spec in
9
- # the spec suite.
10
+ BEFORE_METHOD = :initialize
11
+ AFTER_METHOD = :terminate
12
+
13
+ private_constant :BEFORE_METHOD, :AFTER_METHOD
14
+
15
+ # Executes the given block before each spec in the current context runs.
16
+ #
17
+ # @example
18
+ # require "r_spec"
19
+ #
20
+ # RSpec.describe Integer do
21
+ # before do
22
+ # @value = 123
23
+ # end
24
+ #
25
+ # it { expect(@value).to be 123 }
26
+ #
27
+ # describe "nested" do
28
+ # before do
29
+ # @value -= 81
30
+ # end
31
+ #
32
+ # it { expect(@value).to be 42 }
33
+ # end
34
+ #
35
+ # it { expect(@value).to be 123 }
36
+ # end
37
+ #
38
+ # # Output to the console
39
+ # # Success: expected to be 123.
40
+ # # Success: expected to be 42.
41
+ # # Success: expected to be 123.
10
42
  #
11
43
  # @param block [Proc] The content to execute at the class initialization.
12
44
  def self.before(&block)
13
- define_method(:initialize) do |*args, **kwargs|
45
+ define_method(BEFORE_METHOD) do
14
46
  super()
15
- instance_exec(*args, **kwargs, &block)
47
+ instance_eval(&block)
16
48
  end
49
+
50
+ private BEFORE_METHOD
51
+ end
52
+
53
+ # Executes the given block after each spec in the current context runs.
54
+ #
55
+ # @example
56
+ # require "r_spec"
57
+ #
58
+ # RSpec.describe Integer do
59
+ # after do
60
+ # puts "That is the answer to everything."
61
+ # end
62
+ #
63
+ # it { expect(42).to be 42 }
64
+ # end
65
+ #
66
+ # # Output to the console
67
+ # # Success: expected to be 42.
68
+ # # That is the answer to everything.
69
+ #
70
+ # @param block [Proc] The content to execute at the class initialization.
71
+ def self.after(&block)
72
+ define_method(AFTER_METHOD) do
73
+ instance_exec(&block)
74
+ super()
75
+ end
76
+
77
+ private AFTER_METHOD
17
78
  end
18
79
 
19
80
  # Sets a user-defined property.
20
81
  #
82
+ # @example
83
+ # require "r_spec"
84
+ #
85
+ # RSpec.describe "Name stories" do
86
+ # let(:name) { "Bob" }
87
+ #
88
+ # it { expect(name).to eq "Bob" }
89
+ #
90
+ # context "with last name" do
91
+ # let(:name) { "#{super()} Smith" }
92
+ #
93
+ # it { expect(name).to eq "Bob Smith" }
94
+ # end
95
+ # end
96
+ #
97
+ # # Output to the console
98
+ # # Success: expected to eq "Bob".
99
+ # # Success: expected to eq "Bob Smith".
100
+ #
21
101
  # @param name [String, Symbol] The name of the property.
22
102
  # @param block [Proc] The content of the method to define.
23
103
  #
24
- # @return [Symbol] A protected method that define the block content.
104
+ # @return [Symbol] A private method that define the block content.
25
105
  def self.let(name, *args, **kwargs, &block)
26
- protected define_method(name.to_sym, *args, **kwargs, &block)
106
+ raise Error::ReservedMethod if [BEFORE_METHOD, AFTER_METHOD].include?(name.to_sym)
107
+
108
+ private define_method(name, *args, **kwargs, &block)
27
109
  end
28
110
 
29
- # Sets a user-defined property named `subject`.
111
+ # Sets a user-defined property named {#subject}.
112
+ #
113
+ # @example
114
+ # require "r_spec"
115
+ #
116
+ # RSpec.describe Array do
117
+ # subject { [1, 2, 3] }
118
+ #
119
+ # it "has the prescribed elements" do
120
+ # expect(subject).to eq([1, 2, 3])
121
+ # end
122
+ # end
123
+ #
124
+ # # Output to the console
125
+ # # Success: expected to eq [1, 2, 3].
30
126
  #
31
127
  # @param block [Proc] The subject to set.
32
- # @return [Symbol] A `subject` method that define the block content.
128
+ # @return [Symbol] A {#subject} method that define the block content.
33
129
  def self.subject(&block)
34
130
  let(__method__, &block)
35
131
  end
36
132
 
37
- # Create a group of specs.
133
+ # Defines an example group that describes a unit to be tested.
134
+ #
135
+ # @example
136
+ # require "r_spec"
137
+ #
138
+ # RSpec.describe String do
139
+ # describe "+" do
140
+ # it("concats") { expect("foo" + "bar").to eq "foobar" }
141
+ # end
142
+ # end
38
143
  #
39
- # @param const [Module, #object_id] A module to include in block context.
144
+ # # Output to the console
145
+ # # Success: expected to eq "foobar".
146
+ #
147
+ # @param const [Module, String] A module to include in block context.
40
148
  # @param block [Proc] The block to define the specs.
41
- def self.describe(const = nil, &block)
42
- desc = Sandbox.const_set(random_test_const_name, ::Class.new(self))
43
-
44
- if const.is_a?(::Module)
45
- desc.define_method(:described_class) { const }
46
- desc.send(:protected, :described_class)
47
- end
48
-
149
+ def self.describe(const, &block)
150
+ desc = ::Class.new(self)
151
+ desc.let(:described_class) { const } if const.is_a?(::Module)
49
152
  desc.instance_eval(&block)
50
- desc
51
153
  end
52
154
 
53
- # Add `context` to the DSL.
54
- singleton_class.send(:alias_method, :context, :describe)
155
+ # Defines an example group that establishes a specific context, like _empty
156
+ # array_ versus _array with elements_.
157
+ #
158
+ # It is functionally equivalent to {.describe}.
159
+ #
160
+ # @example
161
+ # require "r_spec"
162
+ #
163
+ # RSpec.describe "web resource" do
164
+ # context "when resource is not found" do
165
+ # pending "responds with 404"
166
+ # end
167
+ #
168
+ # context "when resource is found" do
169
+ # pending "responds with 200"
170
+ # end
171
+ # end
172
+ #
173
+ # # Output to the console
174
+ # # Warning: responds with 404.
175
+ # # Warning: responds with 200.
176
+ #
177
+ # @param _description [String] A description that usually begins with
178
+ # "when", "with" or "without".
179
+ # @param block [Proc] The block to define the specs.
180
+ def self.context(_description = nil, &block)
181
+ desc = ::Class.new(self)
182
+ desc.instance_eval(&block)
183
+ end
55
184
 
56
- # Define a single spec. A spec should contain one or more expectations that
57
- # test the state of the code.
185
+ # Defines a concrete test case.
58
186
  #
187
+ # The test is performed by the block supplied to `&block`.
188
+ #
189
+ # @example The integer after 41
190
+ # require "r_spec"
191
+ #
192
+ # RSpec.describe Integer do
193
+ # it { expect(41.next).to be 42 }
194
+ # end
195
+ #
196
+ # # Output to the console
197
+ # # Success: expected to be 42.
198
+ #
199
+ # @example A division by zero
200
+ # require "r_spec"
201
+ #
202
+ # RSpec.describe Integer do
203
+ # subject { 41 }
204
+ #
205
+ # it { is_expected.to be_an_instance_of described_class }
206
+ #
207
+ # it "raises an error" do
208
+ # expect { subject / 0 }.to raise_exception ZeroDivisionError
209
+ # end
210
+ # end
211
+ #
212
+ # # Output to the console
213
+ # # Success: expected 41 to be an instance of Integer.
214
+ # # Success: divided by 0.
215
+ #
216
+ # It can be used inside a {.describe} or {.context} section.
217
+ #
218
+ # @param _name [String, nil] The name of the spec.
59
219
  # @param block [Proc] An expectation to evaluate.
60
220
  #
61
221
  # @raise (see ExpectationTarget::Base#result)
62
222
  # @return (see ExpectationTarget::Base#result)
63
223
  def self.it(_name = nil, &block)
64
- raise ::ArgumentError, "Missing block" unless block
224
+ raise ::ArgumentError, "Missing example block" unless block
65
225
 
66
- puts "\e[37m#{block.source_location.join(':')}\e[0m"
226
+ example = ::Class.new(self) { include ExpectationHelper::It }.new
227
+ example.instance_eval(&block)
228
+ rescue ::SystemExit
229
+ Console.source(*block.source_location)
67
230
 
68
- i = it_example.new
69
- i.instance_eval(&block)
231
+ exit false
232
+ ensure
233
+ example&.send(AFTER_METHOD)
70
234
  end
71
235
 
72
- # Define a single spec. A spec should contain one or more expectations that
73
- # test the state of the code.
236
+ # Use the {.its} method to define a single spec that specifies the actual
237
+ # value of an attribute of the subject using
238
+ # {ExpectationHelper::Its#is_expected}.
74
239
  #
75
- # @example The value after 41
240
+ # @example The integer after 41
76
241
  # require "r_spec"
77
242
  #
78
243
  # RSpec.describe Integer do
@@ -81,55 +246,100 @@ module RSpec
81
246
  # its(:next) { is_expected.to be 42 }
82
247
  # end
83
248
  #
84
- # @example Without defining a subject
249
+ # # Output to the console
250
+ # # Success: expected to be 42.
251
+ #
252
+ # @example A division by zero
85
253
  # require "r_spec"
86
254
  #
87
255
  # RSpec.describe Integer do
88
- # its(:next) { is_expected.to raise_exception NameError }
256
+ # subject { 41 }
257
+ #
258
+ # its(:/, 0) { is_expected.to raise_exception ZeroDivisionError }
89
259
  # end
90
260
  #
261
+ # # Output to the console
262
+ # # Success: divided by 0.
263
+ #
264
+ # @example A spec without subject
265
+ # require "r_spec"
266
+ #
267
+ # RSpec.describe Integer do
268
+ # its(:boom) { is_expected.to raise_exception RSpec::Error::UndefinedSubject }
269
+ # end
270
+ #
271
+ # # Output to the console
272
+ # # Success: subject not explicitly defined.
273
+ #
91
274
  # @param attribute [String, Symbol] The property to call to subject.
275
+ # @param args [Array] An optional list of arguments.
276
+ # @param kwargs [Hash] An optional list of keyword arguments.
277
+ # @param block [Proc] An expectation to evaluate.
92
278
  #
93
279
  # @raise (see ExpectationTarget::Base#result)
94
280
  # @return (see ExpectationTarget::Base#result)
95
281
  def self.its(attribute, *args, **kwargs, &block)
96
- raise ::ArgumentError, "Missing block" unless block
282
+ raise ::ArgumentError, "Missing example block" unless block
97
283
 
98
- puts "\e[37m#{block.source_location.join(':')}\e[0m"
284
+ example = ::Class.new(self) do
285
+ include ExpectationHelper::Its
99
286
 
100
- i = its_example.new
287
+ define_method(:actual) do
288
+ subject.public_send(attribute, *args, **kwargs)
289
+ end
290
+ end.new
101
291
 
102
- i.define_singleton_method(:actual) do
103
- subject.public_send(attribute, *args, **kwargs)
104
- end
292
+ example.instance_eval(&block)
293
+ rescue ::SystemExit
294
+ Console.source(*block.source_location)
105
295
 
106
- i.instance_eval(&block)
296
+ exit false
297
+ ensure
298
+ example&.send(AFTER_METHOD)
107
299
  end
108
300
 
109
- # @private
301
+ # Defines a pending test case.
302
+ #
303
+ # `&block` is never evaluated. It can be used to describe behaviour that is
304
+ # not yet implemented.
305
+ #
306
+ # @example
307
+ # require "r_spec"
308
+ #
309
+ # RSpec.describe "an example" do
310
+ # pending "is implemented but waiting" do
311
+ # expect something to be finished
312
+ # end
313
+ #
314
+ # pending "is not yet implemented and waiting"
315
+ # end
316
+ #
317
+ # # Output to the console
318
+ # # Warning: is implemented but waiting.
319
+ # # Warning: is not yet implemented and waiting.
320
+ #
321
+ # @param message [String] The reason why the example is pending.
322
+ #
323
+ # @return [nil] Write a message to STDOUT.
110
324
  #
111
- # @return [Class<Dsl>] The class of the example to be tested.
112
- def self.it_example
113
- ::Class.new(self) { include ExpectationHelper::It }
325
+ # @api public
326
+ def self.pending(message)
327
+ Console.passed_spec Error::PendingExpectation.result(message)
114
328
  end
115
329
 
116
- # @private
117
- #
118
- # @return [Class<Dsl>] The class of the example to be tested.
119
- def self.its_example
120
- ::Class.new(self) { include ExpectationHelper::Its }
330
+ private
331
+
332
+ def described_class
333
+ raise Error::UndefinedDescribedClass,
334
+ "the first argument to at least one example group must be a module"
121
335
  end
122
336
 
123
- # @private
124
- #
125
- # @return [String] A random constant name for a test class.
126
- def self.random_test_const_name
127
- "Test#{::SecureRandom.hex(4).to_i(16)}"
337
+ def subject
338
+ raise Error::UndefinedSubject, "subject not explicitly defined"
128
339
  end
129
340
 
130
- private_class_method :it_example, :its_example, :random_test_const_name
341
+ define_method(AFTER_METHOD) do
342
+ # do nothing by default
343
+ end
131
344
  end
132
345
  end
133
-
134
- require_relative "expectation_helper"
135
- require_relative "sandbox"
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative File.join("error", "pending_expectation")
4
+ require_relative File.join("error", "reserved_method")
5
+ require_relative File.join("error", "undefined_described_class")
6
+ require_relative File.join("error", "undefined_subject")
7
+
8
+ module RSpec
9
+ # Namespace for exceptions.
10
+ #
11
+ # @api private
12
+ module Error
13
+ end
14
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "expresenter"
4
+
5
+ module RSpec
6
+ module Error
7
+ # Exception for pending expectations.
8
+ #
9
+ # @api private
10
+ class PendingExpectation < ::RuntimeError
11
+ # @param message [String] The not implemented expectation description.
12
+ #
13
+ # @return [nil] Write a pending expectation to STDOUT.
14
+ def self.result(message)
15
+ ::Expresenter.call(true).with(
16
+ actual: new(message),
17
+ error: nil,
18
+ expected: self,
19
+ got: false,
20
+ matcher: :raise_exception,
21
+ negate: true,
22
+ level: :SHOULD
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module Error
5
+ # Exception for reserved methods.
6
+ #
7
+ # @api private
8
+ class ReservedMethod < ::RuntimeError
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module Error
5
+ # Exception for undefined described classes.
6
+ #
7
+ # @api private
8
+ class UndefinedDescribedClass < ::RuntimeError
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module Error
5
+ # Exception for undefined subjects.
6
+ #
7
+ # @api private
8
+ class UndefinedSubject < ::RuntimeError
9
+ end
10
+ end
11
+ end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative File.join("expectation_helper", "it")
4
+ require_relative File.join("expectation_helper", "its")
5
+
3
6
  module RSpec
4
- # Namespace for `it` and `its` helper modules.
7
+ # Namespace for {Dsl.it} and {Dsl.its}'s helper modules.
5
8
  module ExpectationHelper
6
9
  end
7
10
  end
8
-
9
- require_relative File.join("expectation_helper", "it")
10
- require_relative File.join("expectation_helper", "its")
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base"
3
+ require_relative "shared"
4
4
  require_relative File.join("..", "expectation_target")
5
5
 
6
6
  module RSpec
7
7
  module ExpectationHelper
8
- # `it` expectation helper module.
8
+ # {RSpec::Dsl.it}'s expectation helper module.
9
9
  module It
10
- include Base
10
+ include Shared
11
11
 
12
12
  # Create an expectation for a spec.
13
13
  #
@@ -27,7 +27,7 @@ module RSpec
27
27
 
28
28
  # Wraps the target of an expectation with the subject as actual value.
29
29
  #
30
- # @return (see #expect)
30
+ # @return [Block] The wrapped target of an expectation.
31
31
  #
32
32
  # @example
33
33
  # is_expected # => #<RSpec::ExpectationTarget::Block:0x00007fb6b8263df8 @callable=#<Proc:0x00007fb6b8263e20>>
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base"
3
+ require_relative "shared"
4
4
  require_relative File.join("..", "expectation_target", "block")
5
5
 
6
6
  module RSpec
7
7
  module ExpectationHelper
8
- # `its` expectation helper module.
8
+ # {RSpec::Dsl.its}'s expectation helper module.
9
9
  module Its
10
- include Base
10
+ include Shared
11
11
 
12
12
  # Wraps the target of an expectation with the actual value.
13
13
  #
@@ -2,14 +2,14 @@
2
2
 
3
3
  require "matchi/rspec"
4
4
 
5
- require_relative File.join("..", "pending")
5
+ require_relative File.join("..", "error", "pending_expectation")
6
6
 
7
7
  module RSpec
8
8
  module ExpectationHelper
9
9
  # Abstract expectation helper base module.
10
10
  #
11
11
  # This module defines a number of methods to create expectations, which are
12
- # automatically included into example namespaces.
12
+ # automatically included into examples.
13
13
  #
14
14
  # It also includes a collection of expectation matchers 🤹
15
15
  #
@@ -75,22 +75,8 @@ module RSpec
75
75
  #
76
76
  # @see https://github.com/fixrb/matchi
77
77
  # @see https://github.com/fixrb/matchi-rspec
78
- module Base
78
+ module Shared
79
79
  include ::Matchi::Helper
80
-
81
- # Mark a spec as pending, expectation results will be ignored.
82
- #
83
- # @param description [String] The reason why the example is pending.
84
- #
85
- # @return [nil] Write a message to STDOUT.
86
- #
87
- # @example Output a message to the console and return nil
88
- # pending("something else getting finished") # => nil
89
- #
90
- # @api public
91
- def pending(description)
92
- Pending.result(description)
93
- end
94
80
  end
95
81
  end
96
82
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative File.join("expectation_target", "block")
4
+ require_relative File.join("expectation_target", "value")
5
+
3
6
  module RSpec
4
7
  # Wraps the target of an expectation.
5
8
  #
@@ -25,6 +28,3 @@ module RSpec
25
28
  end
26
29
  end
27
30
  end
28
-
29
- require_relative File.join("expectation_target", "block")
30
- require_relative File.join("expectation_target", "value")
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "expresenter"
4
+ require "test_tube"
5
+
6
+ require_relative File.join("..", "console")
4
7
 
5
8
  module RSpec
6
9
  module ExpectationTarget
@@ -9,6 +12,13 @@ module RSpec
9
12
  # @note `RSpec::ExpectationTarget::Base` is not intended to be instantiated
10
13
  # directly by users. Use `expect` instead.
11
14
  class Base
15
+ # Instantiate a new expectation target.
16
+ #
17
+ # @param actual [#object_id] The actual value of the code to evaluate.
18
+ def initialize(actual)
19
+ @actual = actual
20
+ end
21
+
12
22
  # Runs the given expectation, passing if `matcher` returns true.
13
23
  #
14
24
  # @example _Absolute requirement_ definition
@@ -41,12 +51,12 @@ module RSpec
41
51
 
42
52
  protected
43
53
 
54
+ # @param passed [Boolean] The high expectation passed or failed.
44
55
  # @param actual [#object_id] The actual value.
45
56
  # @param error [Exception, nil] Any raised exception.
46
57
  # @param got [Boolean, nil] Any returned value.
47
58
  # @param matcher [#matches?] The matcher.
48
59
  # @param negate [Boolean] The assertion is positive or negative.
49
- # @param valid [Boolean] The result of an expectation.
50
60
  #
51
61
  # @return [nil] Write a message to STDOUT.
52
62
  #
@@ -54,19 +64,18 @@ module RSpec
54
64
  # `Kernel.exit(false)` with a failure message written to STDERR.
55
65
  #
56
66
  # @api private
57
- def result(actual:, error:, got:, matcher:, negate:, valid:)
58
- puts " " + ::Expresenter.call(valid).with(
67
+ def result(passed, actual:, error:, got:, matcher:, negate:)
68
+ Console.passed_spec ::Expresenter.call(passed).with(
59
69
  actual: actual,
60
70
  error: error,
61
71
  expected: matcher.expected,
62
72
  got: got,
63
73
  negate: negate,
64
- valid: valid,
65
74
  matcher: matcher.class.to_sym,
66
75
  level: :MUST
67
- ).colored_string
76
+ )
68
77
  rescue ::Expresenter::Fail => e
69
- abort " #{e.colored_string}"
78
+ Console.failed_spec(e)
70
79
  end
71
80
  end
72
81
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "spectus/exam"
4
-
5
3
  require_relative "base"
6
4
 
7
5
  module RSpec
@@ -20,15 +18,6 @@ module RSpec
20
18
  # @note `RSpec::ExpectationTarget::Block` is not intended to be instantiated
21
19
  # directly by users. Use `expect` instead.
22
20
  class Block < Base
23
- # Instantiate a new expectation target.
24
- #
25
- # @param block [#call] The code to evaluate.
26
- def initialize(block)
27
- super()
28
-
29
- @callable = block
30
- end
31
-
32
21
  protected
33
22
 
34
23
  # @param matcher [#matches?] The matcher.
@@ -39,20 +28,20 @@ module RSpec
39
28
  # @raise [SystemExit] Terminate execution immediately by calling
40
29
  # `Kernel.exit(false)` with a failure message written to STDERR.
41
30
  def absolute_requirement(matcher:, negate:)
42
- exam = ::Spectus::Exam.new(
43
- callable: @callable,
31
+ experiment = ::TestTube.invoke(
32
+ @actual,
44
33
  isolation: false,
45
- negate: negate,
46
- matcher: matcher
34
+ matcher: matcher,
35
+ negate: negate
47
36
  )
48
37
 
49
38
  result(
50
- actual: exam.actual,
51
- error: exam.exception,
52
- got: exam.got,
39
+ experiment.got.equal?(true),
40
+ actual: experiment.actual,
41
+ error: experiment.error,
42
+ got: experiment.got,
53
43
  matcher: matcher,
54
- negate: negate,
55
- valid: exam.valid?
44
+ negate: negate
56
45
  )
57
46
  end
58
47
  end
@@ -18,15 +18,6 @@ module RSpec
18
18
  # @note `RSpec::ExpectationTarget::Value` is not intended to be instantiated
19
19
  # directly by users. Use `expect` instead.
20
20
  class Value < Base
21
- # Instantiate a new expectation target.
22
- #
23
- # @param actual [#object_id] The actual value.
24
- def initialize(actual)
25
- super()
26
-
27
- @actual = actual
28
- end
29
-
30
21
  protected
31
22
 
32
23
  # @param matcher [#matches?] The matcher.
@@ -37,15 +28,19 @@ module RSpec
37
28
  # @raise [SystemExit] Terminate execution immediately by calling
38
29
  # `Kernel.exit(false)` with a failure message written to STDERR.
39
30
  def absolute_requirement(matcher:, negate:)
40
- valid = negate ^ matcher.matches? { @actual }
31
+ experiment = ::TestTube.pass(
32
+ @actual,
33
+ matcher: matcher,
34
+ negate: negate
35
+ )
41
36
 
42
37
  result(
43
- actual: @actual,
44
- error: nil,
45
- got: valid,
38
+ experiment.got.equal?(true),
39
+ actual: experiment.actual,
40
+ error: experiment.error,
41
+ got: experiment.got,
46
42
  matcher: matcher,
47
- negate: negate,
48
- valid: valid
43
+ negate: negate
49
44
  )
50
45
  end
51
46
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: r_spec
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta4
4
+ version: 1.0.0.beta9
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-06-08 00:00:00.000000000 Z
11
+ date: 2021-06-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: expresenter
@@ -16,42 +16,42 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.2.0
19
+ version: 1.3.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 1.2.0
26
+ version: 1.3.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: matchi-rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.1.1
33
+ version: 1.1.2
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.1
40
+ version: 1.1.2
41
41
  - !ruby/object:Gem::Dependency
42
- name: spectus
42
+ name: test_tube
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 3.3.2
47
+ version: 1.0.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: 3.3.2
54
+ version: 1.0.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -187,17 +187,21 @@ files:
187
187
  - LICENSE.md
188
188
  - README.md
189
189
  - lib/r_spec.rb
190
+ - lib/r_spec/console.rb
190
191
  - lib/r_spec/dsl.rb
192
+ - lib/r_spec/error.rb
193
+ - lib/r_spec/error/pending_expectation.rb
194
+ - lib/r_spec/error/reserved_method.rb
195
+ - lib/r_spec/error/undefined_described_class.rb
196
+ - lib/r_spec/error/undefined_subject.rb
191
197
  - lib/r_spec/expectation_helper.rb
192
- - lib/r_spec/expectation_helper/base.rb
193
198
  - lib/r_spec/expectation_helper/it.rb
194
199
  - lib/r_spec/expectation_helper/its.rb
200
+ - lib/r_spec/expectation_helper/shared.rb
195
201
  - lib/r_spec/expectation_target.rb
196
202
  - lib/r_spec/expectation_target/base.rb
197
203
  - lib/r_spec/expectation_target/block.rb
198
204
  - lib/r_spec/expectation_target/value.rb
199
- - lib/r_spec/pending.rb
200
- - lib/r_spec/sandbox.rb
201
205
  homepage: https://r-spec.dev/
202
206
  licenses:
203
207
  - MIT
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "expresenter"
4
-
5
- module RSpec
6
- # Exception for pending expectations.
7
- #
8
- # @api private
9
- class Pending < ::NotImplementedError
10
- # @param message [String] The not implemented expectation description.
11
- #
12
- # @return [nil] Write a pending expectation to STDOUT.
13
- def self.result(message)
14
- warn " " + ::Expresenter.call(true).with(
15
- actual: new(message),
16
- error: nil,
17
- expected: self,
18
- got: false,
19
- matcher: :raise_exception,
20
- negate: true,
21
- level: :SHOULD,
22
- valid: false
23
- ).colored_string
24
- end
25
- end
26
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RSpec
4
- # Namespace to collect test classes.
5
- #
6
- # @api private
7
- module Sandbox
8
- end
9
- end