r_spec 1.0.0.beta3 → 1.0.0.beta4

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: 7baead9bb63726ef4e29f1ae2df0ff2584177377d1ddda34377d0fb37503ec64
4
- data.tar.gz: 52ebf75f1e76d238ffc7592fd624e6af5bc2eed3423d5742accfcb3222555560
3
+ metadata.gz: 0d94ad6d7ba01dc04c4bcbe727543524f6a3d83cf69549a34453c1eefc5282e9
4
+ data.tar.gz: 61b3ebe1299e69d7ef1f2c180325e85e1bda05e2347176ae507fc7430cf311ca
5
5
  SHA512:
6
- metadata.gz: 985f437d4b2a9ffa63a1a14f9d588ba44bea017e5374ec50ba718c41497a0e91cc41ea7705928c7d7b38b3bbc11f6b6f114929d2e81ba1191be02b248b7e43bf
7
- data.tar.gz: c525c43c2259150b9122bbf6a84bf61cbcd6323109992a7ce9ddd2248143788cbc6c15bf4b4f231648aadba29a8e1fed80dc068d9e2462a2b903299ec9c1c174
6
+ metadata.gz: 5d44ce99f58c17711be4b3f76cdfaf3c9923d031aa9a7d214db50b990825673b9bb485ce095195594fbeb45977763dc5a0be31770f089cd17dcb1e3ef1d6885b
7
+ data.tar.gz: ca64a27f7cf4032b6b073b9fc2eb05aa37654c02a58289c41094e9afa000deccc3886d03d2043642a02e38cf10b6cc0fbd775e7ef9bc1c29cb592b813dcf5d04
data/README.md CHANGED
@@ -1,27 +1,32 @@
1
1
  # RSpec clone
2
2
 
3
- A minimalist [RSpec](https://github.com/rspec/rspec) clone with all the essentials.
3
+ A minimalist __[RSpec](https://github.com/rspec/rspec) clone__ with all the essentials.
4
4
 
5
- ![What did you expect?](https://github.com/cyril/r_spec.rb/raw/main/img/what-did-you-expect.jpg)
5
+ ![What did you RSpec?](https://github.com/cyril/r_spec.rb/raw/main/img/what-did-you-rspec.svg)
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
11
  [![Inline Docs](https://inch-ci.org/github/cyril/r_spec.rb.svg)](https://inch-ci.org/github/cyril/r_spec.rb)
12
+ [![Documentation](https://img.shields.io/:yard-docs-38c800.svg)](https://rubydoc.info/gems/r_spec/frames)
12
13
 
13
14
  ## Goal
14
15
 
15
- This clone attempts to provide most of RSpec's DSL without magic power.
16
+ This clone attempts to provide most of RSpec's DSL to express expected outcomes of a code example without magic power.
16
17
 
17
18
  ## Some differences
18
19
 
19
20
  * Less features and an implementation with much less code complexity.
20
21
  * Spec files can also be executed directly with the `ruby` executable.
21
22
  * There is no option to activate monkey-patching.
22
- * Does not rely on hacks such as `at_exit` hook to trigger the tests.
23
- * Built-in matchers do not trust _actual_ and do not send it any message.
24
- * The subject must be explicitly defined, otherwise it is not implemented.
23
+ * It does not rely on hacks such as `at_exit` hook to trigger the tests.
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.
26
+ * Expectations cannot be added inside a `before` block.
27
+ * [Arbitrary helper methods](https://relishapp.com/rspec/rspec-core/v/3-10/docs/helper-methods/arbitrary-helper-methods) are not exposed to examples.
28
+ * The `let` method defines a helper method rather than a memoized helper method.
29
+ * The one-liner `is_expected` syntax also works with block expectations.
25
30
 
26
31
  ## Important ⚠️
27
32
 
@@ -40,7 +45,7 @@ Following [RubyGems naming conventions](https://guides.rubygems.org/name-your-ge
40
45
  Add this line to your application's Gemfile:
41
46
 
42
47
  ```ruby
43
- gem "r_spec", ">= 1.0.0.beta2"
48
+ gem "r_spec", ">= 1.0.0.beta4"
44
49
  ```
45
50
 
46
51
  And then execute:
@@ -55,223 +60,159 @@ Or install it yourself as:
55
60
  gem install r_spec --pre
56
61
  ```
57
62
 
63
+ ## Overview
64
+
65
+ __RSpec clone__ provides a structure for writing executable examples of how your code should behave.
66
+
67
+ Inspired by [RSpec](https://rspec.info/), it includes a domain specific language (DSL) that allows you to write examples in a way similar to plain english.
68
+
69
+ A basic spec looks something like this:
70
+
71
+ [![Super DRY example](https://asciinema.org/a/418672.svg)](https://asciinema.org/a/418672?autoplay=1)
72
+
58
73
  ## Usage
59
74
 
60
- To understand how the framework builds and runs tests, here are some correspondences between the DSL syntax and the generated Ruby code.
75
+ ### Anatomy of a spec file
61
76
 
62
- ### `describe` method
77
+ To use the `RSpec` module and its DSL, you need to add `require "r_spec"` to your spec files.
78
+ Many projects use a custom spec helper which organizes these includes.
63
79
 
64
- Example of specification content:
80
+ Concrete test cases are defined in `it` blocks.
81
+ An optional descriptive string states it's purpose and a block contains the main logic performing the test.
65
82
 
66
- ```ruby
67
- RSpec.describe String do
68
- end
69
- ```
83
+ An `it` block contains an example that should invoke the code to be tested and define what is expected of it.
84
+ Each example can contain multiple expectations, but it should test only one specific behaviour.
70
85
 
71
- Corresponding Ruby code:
86
+ To express an expectation, wrap an object or block in `expect`, call `to` or `not_to` and pass it a matcher object.
72
87
 
73
- ```ruby
74
- module RSpec::Test
75
- class Test3582143298
76
- protected
77
-
78
- def described_class
79
- String
80
- end
81
- end
82
- end
83
- ```
88
+ If the expectation is met, code execution continues.
89
+ Otherwise the example has _failed_ and other code will not be executed.
84
90
 
85
- ### `context` method
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.
86
92
 
87
- The behavior of the `context` method is exactly the same as `describe`.
93
+ In test files, specs are structured by example groups which are defined by `describe` and `context` sections.
94
+ Typically a top level `describe` defines the outer unit (such as a class) to be tested by the spec.
95
+ Further `describe` sections can be nested within the outer unit to specify smaller units under test (such as individual methods).
88
96
 
89
- ### `subject` method
97
+ For unit tests, it is recommended to follow the conventions for method names:
90
98
 
91
- Example of specification content:
99
+ * outer `describe` is the name of the class, inner `describe` targets methods;
100
+ * instance methods are prefixed with `#`, class methods with `.`.
92
101
 
93
- ```ruby
94
- RSpec.describe "Subject" do
95
- subject do
96
- :foo
97
- end
98
- end
99
- ```
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
+ It has a different name, but behaves exactly like `describe`.
100
104
 
101
- Corresponding Ruby code:
105
+ `describe` and `context` take an optional description as argument and a block containing the individual specs or nested groupings.
102
106
 
103
- ```ruby
104
- module RSpec::Test
105
- class Test3582143298
106
- protected
107
-
108
- def subject
109
- :foo
110
- end
111
- end
112
- end
113
- ```
107
+ ### Expectations
114
108
 
115
- ### Embedded `describe` method
109
+ Expectations define if the value being tested (_actual_) matches a certain value or specific criteria.
116
110
 
117
- Example of specification content:
111
+ #### Equivalence
118
112
 
119
113
  ```ruby
120
- RSpec.describe "Describe" do
121
- # main describe block
122
-
123
- describe "Embedded describe" do
124
- # embedded describe block
125
- end
126
- end
114
+ expect(actual).to eql(expected) # passes if expected.eql?(actual)
115
+ expect(actual).to eq(expected) # passes if expected.eql?(actual)
127
116
  ```
128
117
 
129
- Corresponding Ruby code:
118
+ #### Identity
130
119
 
131
120
  ```ruby
132
- module RSpec::Test
133
- class Test3582143298
134
- # main describe block
135
- end
136
- end
137
-
138
- module RSpec::Test
139
- class Test198623541 < RSpec::Test::Test3582143298
140
- # embedded describe block
141
- end
142
- end
121
+ expect(actual).to equal(expected) # passes if expected.equal?(actual)
122
+ expect(actual).to be(expected) # passes if expected.equal?(actual)
143
123
  ```
144
124
 
145
- ### `let` method
146
-
147
- Example of specification content:
125
+ #### Regular expressions
148
126
 
149
127
  ```ruby
150
- RSpec.describe do
151
- let(:var0) { 42 }
152
- let(:var1) { 42 + var3 }
153
- let(:var3) { 42 }
154
- end
128
+ expect(actual).to match(expected) # passes if expected.match?(actual)
155
129
  ```
156
130
 
157
- Corresponding Ruby code:
131
+ #### Expecting errors
158
132
 
159
133
  ```ruby
160
- module RSpec::Test
161
- class Test3582143298
162
- protected
163
-
164
- def var0
165
- 42
166
- end
167
-
168
- def var1
169
- 42 + var3
170
- end
171
-
172
- def var3
173
- 42
174
- end
175
- end
176
- end
134
+ expect { actual }.to raise_exception(expected) # passes if expected exception is raised
177
135
  ```
178
136
 
179
- ### `before` method
180
-
181
- Example of specification content:
137
+ #### Truth
182
138
 
183
139
  ```ruby
184
- RSpec.describe do
185
- before do
186
- puts "hello"
187
- end
188
- end
140
+ expect(actual).to be_true # passes if true.equal?(actual)
189
141
  ```
190
142
 
191
- Corresponding Ruby code:
143
+ #### Untruth
192
144
 
193
145
  ```ruby
194
- module RSpec::Test
195
- class Test3582143298
196
- def initialize
197
- puts "hello"
198
- end
199
- end
200
- end
146
+ expect(actual).to be_false # passes if false.equal?(actual)
201
147
  ```
202
148
 
203
- ### `expect` method
204
-
205
- Example of specification content:
149
+ #### Nil
206
150
 
207
151
  ```ruby
208
- RSpec.describe do
209
- it { expect(41.next).to be(42) }
210
- end
152
+ expect(actual).to be_nil # passes if nil.equal?(actual)
211
153
  ```
212
154
 
213
- Corresponding Ruby code:
155
+ #### Type/class
214
156
 
215
157
  ```ruby
216
- module RSpec::Test
217
- class Test3582143298
218
- end
219
- end
158
+ expect(actual).to be_instance_of(expected) # passes if expected.equal?(actual.class)
159
+ expect(actual).to be_an_instance_of(expected) # passes if expected.equal?(actual.class)
160
+ ```
220
161
 
221
- example_class = Class.new(RSpec::Test::Test3582143298) do
222
- include Matchi::Helper
162
+ ### Running specs
223
163
 
224
- # Declaration of private methods (`expect`, `is_expected`, `log`, `pending`).
225
- end
164
+ By convention, specs live in the `spec/` directory of a project. Spec files should end with `_spec.rb` to be recognizable as such.
226
165
 
227
- example_class.new.instance_eval do
228
- ExpectationTarget::Value.new(41.next).to be(42)
229
- end
166
+ Depending of the project settings, you may run the specs of a project by running `rake spec` (see [`rake` integration example](#rake-integration-example) below).
167
+ A single file can also be executed directly with the Ruby interpreter.
168
+
169
+ #### Examples
170
+
171
+ Run all specs in files matching `spec/**/*_spec.rb`:
172
+
173
+ ```sh
174
+ bundle exec rake spec
230
175
  ```
231
176
 
232
- Success: expected to be 42.
177
+ Run a single file:
233
178
 
234
- ## Example
179
+ ```sh
180
+ ruby spec/my/test/file_spec.rb
181
+ ```
235
182
 
236
- Let's test an array:
183
+ I know that sounds weird, but the [`rspec` command line](https://relishapp.com/rspec/rspec-core/docs/command-line) is also working pretty well:
237
184
 
238
- ```ruby
239
- # array_spec.rb
185
+ ```sh
186
+ rspec spec/my/test/file_spec.rb
187
+ rspec spec/my/test/file_spec.rb:42
188
+ rspec spec/my/test/
189
+ rspec
190
+ ```
240
191
 
241
- require "r_spec"
192
+ ### Spec helper
242
193
 
243
- RSpec.describe Array do
244
- before do
245
- @elements = described_class.new
246
- end
194
+ Many projects use a custom spec helper file, usually named `spec/spec_helper.rb`.
247
195
 
248
- describe "#count" do
249
- subject do
250
- @elements.count
251
- end
196
+ This file is used to require `r_spec` and other includes, like the code from the project needed for every spec file.
252
197
 
253
- it { is_expected.to be 0 }
198
+ ### `rake` integration example
254
199
 
255
- context "when a new element is added" do
256
- before do
257
- @elements << 1
258
- end
200
+ The following `Rakefile` settings should be enough:
259
201
 
260
- it { is_expected.to be 1 }
261
- end
262
- end
263
- end
264
- ```
202
+ ```ruby
203
+ require "bundler/gem_tasks"
204
+ require "rake/testtask"
265
205
 
266
- It can be tested in the console with the command:
206
+ Rake::TestTask.new do |t|
207
+ t.pattern = "spec/**/*_spec.rb"
208
+ t.verbose = true
209
+ t.warning = true
210
+ end
267
211
 
268
- ```sh
269
- ruby array_spec.rb
212
+ task spec: :test
213
+ task default: :test
270
214
  ```
271
215
 
272
- array_spec.rb:15 Success: expected to be 0.
273
- array_spec.rb:22 Success: expected to be 1.
274
-
275
216
  ## Test suite
276
217
 
277
218
  __RSpec clone__'s specifications are self-described here: [spec/](https://github.com/cyril/r_spec.rb/blob/main/spec/)
data/lib/r_spec.rb CHANGED
@@ -2,11 +2,51 @@
2
2
 
3
3
  # Top level namespace for the RSpec clone.
4
4
  #
5
- # @example
5
+ # @example The true from the false
6
+ # require "r_spec"
7
+ #
8
+ # RSpec.describe do
9
+ # it { expect(false).not_to be true }
10
+ # end
11
+ #
12
+ # @example The basic behavior of arrays
13
+ # require "r_spec"
14
+ #
15
+ # RSpec.describe Array do
16
+ # describe "#size" do
17
+ # it "correctly reports the number of elements in the Array" do
18
+ # expect([1, 2, 3].size).to eq 3
19
+ # end
20
+ # end
21
+ #
22
+ # describe "#empty?" do
23
+ # it "is empty when no elements are in the array" do
24
+ # expect([].empty?).to be_true
25
+ # end
26
+ #
27
+ # it "is not empty if there are elements in the array" do
28
+ # expect([1].empty?).to be_false
29
+ # end
30
+ # end
31
+ # end
32
+ #
33
+ # @example An inherited definition of let
6
34
  # require "r_spec"
7
35
  #
8
36
  # RSpec.describe Integer do
9
- # it { expect(41.next).to be 42 }
37
+ # let(:answer) { 42 }
38
+ #
39
+ # it "returns the value" do
40
+ # expect(answer).to be(42)
41
+ # end
42
+ #
43
+ # context "when the number is incremented" do
44
+ # let(:answer) { super().next }
45
+ #
46
+ # it "returns the next value" do
47
+ # expect(answer).to be(43)
48
+ # end
49
+ # end
10
50
  # end
11
51
  #
12
52
  # @api public
@@ -17,8 +57,8 @@ module RSpec
17
57
  # @param block [Proc] The block to define the specs.
18
58
  #
19
59
  # @api public
20
- def self.describe(const, &block)
21
- DSL.describe(const, &block)
60
+ def self.describe(const = nil, &block)
61
+ Dsl.describe(const, &block)
22
62
  end
23
63
  end
24
64
 
data/lib/r_spec/dsl.rb CHANGED
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "matchi/rspec"
4
3
  require "securerandom"
5
4
 
6
5
  module RSpec
7
6
  # Abstract class for handling the domain-specific language.
8
- class DSL
7
+ class Dsl
8
+ # Instructs the spec runner to execute the given block before each spec in
9
+ # the spec suite.
10
+ #
9
11
  # @param block [Proc] The content to execute at the class initialization.
10
12
  def self.before(&block)
11
13
  define_method(:initialize) do |*args, **kwargs|
@@ -14,24 +16,30 @@ module RSpec
14
16
  end
15
17
  end
16
18
 
17
- # @param block [Proc] The content of the method to define.
19
+ # Sets a user-defined property.
20
+ #
21
+ # @param name [String, Symbol] The name of the property.
22
+ # @param block [Proc] The content of the method to define.
23
+ #
18
24
  # @return [Symbol] A protected method that define the block content.
19
- def self.let(name, &block)
20
- protected define_method(name.to_sym, &block)
25
+ def self.let(name, *args, **kwargs, &block)
26
+ protected define_method(name.to_sym, *args, **kwargs, &block)
21
27
  end
22
28
 
29
+ # Sets a user-defined property named `subject`.
30
+ #
23
31
  # @param block [Proc] The subject to set.
24
32
  # @return [Symbol] A `subject` method that define the block content.
25
33
  def self.subject(&block)
26
34
  let(__method__, &block)
27
35
  end
28
36
 
29
- # Describe a set of expectations.
37
+ # Create a group of specs.
30
38
  #
31
39
  # @param const [Module, #object_id] A module to include in block context.
32
40
  # @param block [Proc] The block to define the specs.
33
- def self.describe(const, &block)
34
- desc = Test.const_set(random_test_const_name, ::Class.new(self))
41
+ def self.describe(const = nil, &block)
42
+ desc = Sandbox.const_set(random_test_const_name, ::Class.new(self))
35
43
 
36
44
  if const.is_a?(::Module)
37
45
  desc.define_method(:described_class) { const }
@@ -45,7 +53,8 @@ module RSpec
45
53
  # Add `context` to the DSL.
46
54
  singleton_class.send(:alias_method, :context, :describe)
47
55
 
48
- # Evaluate an expectation.
56
+ # Define a single spec. A spec should contain one or more expectations that
57
+ # test the state of the code.
49
58
  #
50
59
  # @param block [Proc] An expectation to evaluate.
51
60
  #
@@ -56,55 +65,59 @@ module RSpec
56
65
 
57
66
  puts "\e[37m#{block.source_location.join(':')}\e[0m"
58
67
 
59
- i = example.new
68
+ i = it_example.new
60
69
  i.instance_eval(&block)
61
70
  end
62
71
 
63
- # @private
72
+ # Define a single spec. A spec should contain one or more expectations that
73
+ # test the state of the code.
74
+ #
75
+ # @example The value after 41
76
+ # require "r_spec"
77
+ #
78
+ # RSpec.describe Integer do
79
+ # subject { 41 }
64
80
  #
65
- # @return [Class<DSL>] The class of the example to be tested.
66
- def self.example
67
- # Dynamic creation of an example class.
68
- ::Class.new(self) do
69
- # Include a collection of matchers.
70
- include ::Matchi::Helper
71
-
72
- private
73
-
74
- # Wraps the target of an expectation with the actual value.
75
- #
76
- # @param actual [#object_id] The actual value.
77
- #
78
- # @return [ExpectationTarget] The target of the expectation.
79
- def expect(actual = self.class.superclass, &block)
80
- ExpectationTarget.call(self.class.superclass, actual, block)
81
- end
82
-
83
- # Wraps the target of an expectation with the subject as actual value.
84
- #
85
- # @return [ExpectationTarget] (see #expect)
86
- def is_expected
87
- expect(subject)
88
- end
89
-
90
- # Output a message to the console.
91
- #
92
- # @param message [String] The string to be notified about.
93
- #
94
- # @return [nil] Write a message to STDOUT.
95
- def log(message)
96
- Log.result(message)
97
- end
98
-
99
- # Indicate that an example is disabled pending some action.
100
- #
101
- # @param description [String] The reason why the example is pending.
102
- #
103
- # @return [nil] Write a message to STDOUT.
104
- def pending(description)
105
- Pending.result(description)
106
- end
81
+ # its(:next) { is_expected.to be 42 }
82
+ # end
83
+ #
84
+ # @example Without defining a subject
85
+ # require "r_spec"
86
+ #
87
+ # RSpec.describe Integer do
88
+ # its(:next) { is_expected.to raise_exception NameError }
89
+ # end
90
+ #
91
+ # @param attribute [String, Symbol] The property to call to subject.
92
+ #
93
+ # @raise (see ExpectationTarget::Base#result)
94
+ # @return (see ExpectationTarget::Base#result)
95
+ def self.its(attribute, *args, **kwargs, &block)
96
+ raise ::ArgumentError, "Missing block" unless block
97
+
98
+ puts "\e[37m#{block.source_location.join(':')}\e[0m"
99
+
100
+ i = its_example.new
101
+
102
+ i.define_singleton_method(:actual) do
103
+ subject.public_send(attribute, *args, **kwargs)
107
104
  end
105
+
106
+ i.instance_eval(&block)
107
+ end
108
+
109
+ # @private
110
+ #
111
+ # @return [Class<Dsl>] The class of the example to be tested.
112
+ def self.it_example
113
+ ::Class.new(self) { include ExpectationHelper::It }
114
+ end
115
+
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 }
108
121
  end
109
122
 
110
123
  # @private
@@ -114,11 +127,9 @@ module RSpec
114
127
  "Test#{::SecureRandom.hex(4).to_i(16)}"
115
128
  end
116
129
 
117
- private_class_method :example, :random_test_const_name
130
+ private_class_method :it_example, :its_example, :random_test_const_name
118
131
  end
119
132
  end
120
133
 
121
- require_relative "expectation_target"
122
- require_relative "log"
123
- require_relative "pending"
124
- require_relative "test"
134
+ require_relative "expectation_helper"
135
+ require_relative "sandbox"
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ # Namespace for `it` and `its` helper modules.
5
+ module ExpectationHelper
6
+ end
7
+ end
8
+
9
+ require_relative File.join("expectation_helper", "it")
10
+ require_relative File.join("expectation_helper", "its")
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "matchi/rspec"
4
+
5
+ require_relative File.join("..", "pending")
6
+
7
+ module RSpec
8
+ module ExpectationHelper
9
+ # Abstract expectation helper base module.
10
+ #
11
+ # This module defines a number of methods to create expectations, which are
12
+ # automatically included into example namespaces.
13
+ #
14
+ # It also includes a collection of expectation matchers 🤹
15
+ #
16
+ # @example Equivalence matcher
17
+ # matcher = eql("foo") # => Matchi::Matcher::Eql.new("foo")
18
+ # matcher.matches? { "foo" } # => true
19
+ # matcher.matches? { "bar" } # => false
20
+ #
21
+ # matcher = eq("foo") # => Matchi::Matcher::Eq.new("foo")
22
+ # matcher.matches? { "foo" } # => true
23
+ # matcher.matches? { "bar" } # => false
24
+ #
25
+ # @example Identity matcher
26
+ # object = "foo"
27
+ #
28
+ # matcher = equal(object) # => Matchi::Matcher::Equal.new(object)
29
+ # matcher.matches? { object } # => true
30
+ # matcher.matches? { "foo" } # => false
31
+ #
32
+ # matcher = be(object) # => Matchi::Matcher::Be.new(object)
33
+ # matcher.matches? { object } # => true
34
+ # matcher.matches? { "foo" } # => false
35
+ #
36
+ # @example Regular expressions matcher
37
+ # matcher = match(/^foo$/) # => Matchi::Matcher::Match.new(/^foo$/)
38
+ # matcher.matches? { "foo" } # => true
39
+ # matcher.matches? { "bar" } # => false
40
+ #
41
+ # @example Expecting errors matcher
42
+ # matcher = raise_exception(NameError) # => Matchi::Matcher::RaiseException.new(NameError)
43
+ # matcher.matches? { Boom } # => true
44
+ # matcher.matches? { true } # => false
45
+ #
46
+ # @example Truth matcher
47
+ # matcher = be_true # => Matchi::Matcher::BeTrue.new
48
+ # matcher.matches? { true } # => true
49
+ # matcher.matches? { false } # => false
50
+ # matcher.matches? { nil } # => false
51
+ # matcher.matches? { 4 } # => false
52
+ #
53
+ # @example Untruth matcher
54
+ # matcher = be_false # => Matchi::Matcher::BeFalse.new
55
+ # matcher.matches? { false } # => true
56
+ # matcher.matches? { true } # => false
57
+ # matcher.matches? { nil } # => false
58
+ # matcher.matches? { 4 } # => false
59
+ #
60
+ # @example Nil matcher
61
+ # matcher = be_nil # => Matchi::Matcher::BeNil.new
62
+ # matcher.matches? { nil } # => true
63
+ # matcher.matches? { false } # => false
64
+ # matcher.matches? { true } # => false
65
+ # matcher.matches? { 4 } # => false
66
+ #
67
+ # @example Type/class matcher
68
+ # matcher = be_instance_of(String) # => Matchi::Matcher::BeInstanceOf.new(String)
69
+ # matcher.matches? { "foo" } # => true
70
+ # matcher.matches? { 4 } # => false
71
+ #
72
+ # matcher = be_an_instance_of(String) # => Matchi::Matcher::BeAnInstanceOf.new(String)
73
+ # matcher.matches? { "foo" } # => true
74
+ # matcher.matches? { 4 } # => false
75
+ #
76
+ # @see https://github.com/fixrb/matchi
77
+ # @see https://github.com/fixrb/matchi-rspec
78
+ module Base
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
+ end
95
+ end
96
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative File.join("..", "expectation_target")
5
+
6
+ module RSpec
7
+ module ExpectationHelper
8
+ # `it` expectation helper module.
9
+ module It
10
+ include Base
11
+
12
+ # Create an expectation for a spec.
13
+ #
14
+ # @param value [#object_id, nil] An actual value.
15
+ # @param block [#call, nil] A code to evaluate.
16
+ #
17
+ # @return [Block, Value] The wrapped target of an expectation.
18
+ #
19
+ # @example
20
+ # expect("foo") # => #<RSpec::ExpectationTarget::Value:0x00007fb6b82311a0 @actual="foo">
21
+ # expect { Boom } # => #<RSpec::ExpectationTarget::Block:0x00007fb6b8263df8 @callable=#<Proc:0x00007fb6b8263e20>>
22
+ #
23
+ # @api public
24
+ def expect(value = self.class.superclass, &block)
25
+ ExpectationTarget.call(self.class.superclass, value, block)
26
+ end
27
+
28
+ # Wraps the target of an expectation with the subject as actual value.
29
+ #
30
+ # @return (see #expect)
31
+ #
32
+ # @example
33
+ # is_expected # => #<RSpec::ExpectationTarget::Block:0x00007fb6b8263df8 @callable=#<Proc:0x00007fb6b8263e20>>
34
+ #
35
+ # @api public
36
+ def is_expected
37
+ expect { subject }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative File.join("..", "expectation_target", "block")
5
+
6
+ module RSpec
7
+ module ExpectationHelper
8
+ # `its` expectation helper module.
9
+ module Its
10
+ include Base
11
+
12
+ # Wraps the target of an expectation with the actual value.
13
+ #
14
+ # @return [Block] The wrapped target of an expectation.
15
+ #
16
+ # @example
17
+ # is_expected # => #<RSpec::ExpectationTarget::Block:0x00007fb6b8263df8 @callable=#<Proc:0x00007fb6b8263e20>>
18
+ #
19
+ # @api public
20
+ def is_expected
21
+ ExpectationTarget::Block.new(method(:actual))
22
+ end
23
+ end
24
+ end
25
+ end
@@ -2,14 +2,16 @@
2
2
 
3
3
  module RSpec
4
4
  # Wraps the target of an expectation.
5
+ #
6
+ # @api private
5
7
  module ExpectationTarget
6
8
  # @param undefined_value A sentinel value to be able to tell when the user
7
9
  # did not pass an argument. We can't use `nil` for that because `nil` is a
8
10
  # valid value to pass.
9
- # @param value [#object_id, nil] An actual value
11
+ # @param value [#object_id, nil] An actual value.
10
12
  # @param block [#call, nil] A code to evaluate.
11
13
  #
12
- # @private
14
+ # @return [Block, Value] The wrapped target of an expectation.
13
15
  def self.call(undefined_value, value, block)
14
16
  if undefined_value.equal?(value)
15
17
  raise ::ArgumentError, "Pass either an argument or a block" unless block
@@ -4,9 +4,10 @@ require "expresenter"
4
4
 
5
5
  module RSpec
6
6
  module ExpectationTarget
7
- # Abstract class.
7
+ # Abstract expectation target base class.
8
8
  #
9
- # @private
9
+ # @note `RSpec::ExpectationTarget::Base` is not intended to be instantiated
10
+ # directly by users. Use `expect` instead.
10
11
  class Base
11
12
  # Runs the given expectation, passing if `matcher` returns true.
12
13
  #
@@ -17,6 +18,8 @@ module RSpec
17
18
  #
18
19
  # @raise (see #result)
19
20
  # @return (see #result)
21
+ #
22
+ # @api public
20
23
  def to(matcher)
21
24
  absolute_requirement(matcher: matcher, negate: false)
22
25
  end
@@ -30,6 +33,8 @@ module RSpec
30
33
  #
31
34
  # @raise (see #result)
32
35
  # @return (see #result)
36
+ #
37
+ # @api public
33
38
  def not_to(matcher)
34
39
  absolute_requirement(matcher: matcher, negate: true)
35
40
  end
@@ -47,6 +52,8 @@ module RSpec
47
52
  #
48
53
  # @raise [SystemExit] Terminate execution immediately by calling
49
54
  # `Kernel.exit(false)` with a failure message written to STDERR.
55
+ #
56
+ # @api private
50
57
  def result(actual:, error:, got:, matcher:, negate:, valid:)
51
58
  puts " " + ::Expresenter.call(valid).with(
52
59
  actual: actual,
@@ -6,7 +6,7 @@ require_relative "base"
6
6
 
7
7
  module RSpec
8
8
  module ExpectationTarget
9
- # Wraps the target of an expectation.
9
+ # Wraps the target of an expectation with a block.
10
10
  #
11
11
  # @example
12
12
  # expect { something } # => ExpectationTarget::Block wrapping something
@@ -19,14 +19,10 @@ module RSpec
19
19
  #
20
20
  # @note `RSpec::ExpectationTarget::Block` is not intended to be instantiated
21
21
  # directly by users. Use `expect` instead.
22
- #
23
- # @private
24
22
  class Block < Base
25
23
  # Instantiate a new expectation target.
26
24
  #
27
25
  # @param block [#call] The code to evaluate.
28
- #
29
- # @api private
30
26
  def initialize(block)
31
27
  super()
32
28
 
@@ -36,10 +32,12 @@ module RSpec
36
32
  protected
37
33
 
38
34
  # @param matcher [#matches?] The matcher.
39
- # @param negate [Boolean] Positive or negative assertion?
35
+ # @param negate [Boolean] The assertion is positive or negative.
36
+ #
37
+ # @return [nil] Write a message to STDOUT.
40
38
  #
41
- # @raise (see Base#result)
42
- # @return (see Base#result)
39
+ # @raise [SystemExit] Terminate execution immediately by calling
40
+ # `Kernel.exit(false)` with a failure message written to STDERR.
43
41
  def absolute_requirement(matcher:, negate:)
44
42
  exam = ::Spectus::Exam.new(
45
43
  callable: @callable,
@@ -4,7 +4,7 @@ require_relative "base"
4
4
 
5
5
  module RSpec
6
6
  module ExpectationTarget
7
- # Wraps the target of an expectation.
7
+ # Wraps the target of an expectation with a value.
8
8
  #
9
9
  # @example
10
10
  # expect(something) # => ExpectationTarget::Value wrapping something
@@ -17,14 +17,10 @@ module RSpec
17
17
  #
18
18
  # @note `RSpec::ExpectationTarget::Value` is not intended to be instantiated
19
19
  # directly by users. Use `expect` instead.
20
- #
21
- # @private
22
20
  class Value < Base
23
21
  # Instantiate a new expectation target.
24
22
  #
25
23
  # @param actual [#object_id] The actual value.
26
- #
27
- # @api private
28
24
  def initialize(actual)
29
25
  super()
30
26
 
@@ -34,10 +30,12 @@ module RSpec
34
30
  protected
35
31
 
36
32
  # @param matcher [#matches?] The matcher.
37
- # @param negate [Boolean] Positive or negative assertion?
33
+ # @param negate [Boolean] The assertion is positive or negative.
34
+ #
35
+ # @return [nil] Write a message to STDOUT.
38
36
  #
39
- # @raise (see Base#result)
40
- # @return (see Base#result)
37
+ # @raise [SystemExit] Terminate execution immediately by calling
38
+ # `Kernel.exit(false)` with a failure message written to STDERR.
41
39
  def absolute_requirement(matcher:, negate:)
42
40
  valid = negate ^ matcher.matches? { @actual }
43
41
 
@@ -4,6 +4,8 @@ require "expresenter"
4
4
 
5
5
  module RSpec
6
6
  # Exception for pending expectations.
7
+ #
8
+ # @api private
7
9
  class Pending < ::NotImplementedError
8
10
  # @param message [String] The not implemented expectation description.
9
11
  #
@@ -2,6 +2,8 @@
2
2
 
3
3
  module RSpec
4
4
  # Namespace to collect test classes.
5
- module Test
5
+ #
6
+ # @api private
7
+ module Sandbox
6
8
  end
7
9
  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.beta3
4
+ version: 1.0.0.beta4
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-05 00:00:00.000000000 Z
11
+ date: 2021-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: expresenter
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.1.0
33
+ version: 1.1.1
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.0
40
+ version: 1.1.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: spectus
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -188,13 +188,16 @@ files:
188
188
  - README.md
189
189
  - lib/r_spec.rb
190
190
  - lib/r_spec/dsl.rb
191
+ - lib/r_spec/expectation_helper.rb
192
+ - lib/r_spec/expectation_helper/base.rb
193
+ - lib/r_spec/expectation_helper/it.rb
194
+ - lib/r_spec/expectation_helper/its.rb
191
195
  - lib/r_spec/expectation_target.rb
192
196
  - lib/r_spec/expectation_target/base.rb
193
197
  - lib/r_spec/expectation_target/block.rb
194
198
  - lib/r_spec/expectation_target/value.rb
195
- - lib/r_spec/log.rb
196
199
  - lib/r_spec/pending.rb
197
- - lib/r_spec/test.rb
200
+ - lib/r_spec/sandbox.rb
198
201
  homepage: https://r-spec.dev/
199
202
  licenses:
200
203
  - MIT
@@ -202,6 +205,7 @@ metadata:
202
205
  bug_tracker_uri: https://github.com/cyril/r_spec.rb/issues
203
206
  documentation_uri: https://rubydoc.info/gems/r_spec
204
207
  source_code_uri: https://github.com/cyril/r_spec.rb
208
+ wiki_uri: https://github.com/cyril/r_spec.rb/wiki
205
209
  post_install_message:
206
210
  rdoc_options: []
207
211
  require_paths:
data/lib/r_spec/log.rb DELETED
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "expresenter"
4
-
5
- module RSpec
6
- # Exception for debugging purpose.
7
- class Log < ::NoMethodError
8
- # @param message [String] A message to log to the console.
9
- #
10
- # @return [nil] Write a log message to STDOUT.
11
- def self.result(message)
12
- puts " " + ::Expresenter.call(true).with(
13
- actual: nil,
14
- error: new(message),
15
- expected: 42,
16
- got: nil,
17
- matcher: :be,
18
- negate: false,
19
- level: :MAY,
20
- valid: false
21
- ).colored_string
22
- end
23
- end
24
- end