r_spec 1.0.0.beta2 → 1.0.0.beta3

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: d95780dc86d2b0c2295f22fe18ed889298716d8a817ba4a74fbd1c9c1ec71c03
4
- data.tar.gz: a954787c7ad5a50c1d3a49d5b1be21515dc0cad0ecd5f7fa618ee736014add2f
3
+ metadata.gz: 7baead9bb63726ef4e29f1ae2df0ff2584177377d1ddda34377d0fb37503ec64
4
+ data.tar.gz: 52ebf75f1e76d238ffc7592fd624e6af5bc2eed3423d5742accfcb3222555560
5
5
  SHA512:
6
- metadata.gz: 338b22198f2f09e9ae3a36184027f23f728ebdc7b691e0ef3f05d111bf3d753c829d21388dba52fdc8be0e8ba452deea5cc08d38c848e932d67fb1e448769600
7
- data.tar.gz: 6601841bac1ed4603c327539fc5b123b9990cfa483b42504ed18dc8680d9c304636dbff10a05ebdc6f76de6d11ac3190b9ad77453aaeb978f853360f9330e04e
6
+ metadata.gz: 985f437d4b2a9ffa63a1a14f9d588ba44bea017e5374ec50ba718c41497a0e91cc41ea7705928c7d7b38b3bbc11f6b6f114929d2e81ba1191be02b248b7e43bf
7
+ data.tar.gz: c525c43c2259150b9122bbf6a84bf61cbcd6323109992a7ce9ddd2248143788cbc6c15bf4b4f231648aadba29a8e1fed80dc068d9e2462a2b903299ec9c1c174
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # RSpec clone
2
2
 
3
- A minimalist [RSpec](https://github.com/rspec/rspec) clone with an emphasis on correctness and simplicity.
3
+ A minimalist [RSpec](https://github.com/rspec/rspec) clone with all the essentials.
4
4
 
5
5
  ![What did you expect?](https://github.com/cyril/r_spec.rb/raw/main/img/what-did-you-expect.jpg)
6
6
 
@@ -12,7 +12,7 @@ A minimalist [RSpec](https://github.com/rspec/rspec) clone with an emphasis on c
12
12
 
13
13
  ## Goal
14
14
 
15
- This clone attempts to provide most of RSpec's DSL without magic power, so that its code could reasonably become less complex than the code of your application.
15
+ This clone attempts to provide most of RSpec's DSL without magic power.
16
16
 
17
17
  ## Some differences
18
18
 
@@ -57,6 +57,182 @@ gem install r_spec --pre
57
57
 
58
58
  ## Usage
59
59
 
60
+ To understand how the framework builds and runs tests, here are some correspondences between the DSL syntax and the generated Ruby code.
61
+
62
+ ### `describe` method
63
+
64
+ Example of specification content:
65
+
66
+ ```ruby
67
+ RSpec.describe String do
68
+ end
69
+ ```
70
+
71
+ Corresponding Ruby code:
72
+
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
+ ```
84
+
85
+ ### `context` method
86
+
87
+ The behavior of the `context` method is exactly the same as `describe`.
88
+
89
+ ### `subject` method
90
+
91
+ Example of specification content:
92
+
93
+ ```ruby
94
+ RSpec.describe "Subject" do
95
+ subject do
96
+ :foo
97
+ end
98
+ end
99
+ ```
100
+
101
+ Corresponding Ruby code:
102
+
103
+ ```ruby
104
+ module RSpec::Test
105
+ class Test3582143298
106
+ protected
107
+
108
+ def subject
109
+ :foo
110
+ end
111
+ end
112
+ end
113
+ ```
114
+
115
+ ### Embedded `describe` method
116
+
117
+ Example of specification content:
118
+
119
+ ```ruby
120
+ RSpec.describe "Describe" do
121
+ # main describe block
122
+
123
+ describe "Embedded describe" do
124
+ # embedded describe block
125
+ end
126
+ end
127
+ ```
128
+
129
+ Corresponding Ruby code:
130
+
131
+ ```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
143
+ ```
144
+
145
+ ### `let` method
146
+
147
+ Example of specification content:
148
+
149
+ ```ruby
150
+ RSpec.describe do
151
+ let(:var0) { 42 }
152
+ let(:var1) { 42 + var3 }
153
+ let(:var3) { 42 }
154
+ end
155
+ ```
156
+
157
+ Corresponding Ruby code:
158
+
159
+ ```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
177
+ ```
178
+
179
+ ### `before` method
180
+
181
+ Example of specification content:
182
+
183
+ ```ruby
184
+ RSpec.describe do
185
+ before do
186
+ puts "hello"
187
+ end
188
+ end
189
+ ```
190
+
191
+ Corresponding Ruby code:
192
+
193
+ ```ruby
194
+ module RSpec::Test
195
+ class Test3582143298
196
+ def initialize
197
+ puts "hello"
198
+ end
199
+ end
200
+ end
201
+ ```
202
+
203
+ ### `expect` method
204
+
205
+ Example of specification content:
206
+
207
+ ```ruby
208
+ RSpec.describe do
209
+ it { expect(41.next).to be(42) }
210
+ end
211
+ ```
212
+
213
+ Corresponding Ruby code:
214
+
215
+ ```ruby
216
+ module RSpec::Test
217
+ class Test3582143298
218
+ end
219
+ end
220
+
221
+ example_class = Class.new(RSpec::Test::Test3582143298) do
222
+ include Matchi::Helper
223
+
224
+ # Declaration of private methods (`expect`, `is_expected`, `log`, `pending`).
225
+ end
226
+
227
+ example_class.new.instance_eval do
228
+ ExpectationTarget::Value.new(41.next).to be(42)
229
+ end
230
+ ```
231
+
232
+ Success: expected to be 42.
233
+
234
+ ## Example
235
+
60
236
  Let's test an array:
61
237
 
62
238
  ```ruby
@@ -104,12 +280,19 @@ __RSpec clone__'s specifications are self-described here: [spec/](https://github
104
280
 
105
281
  * Home page: https://r-spec.dev
106
282
  * Source code: https://github.com/cyril/r_spec.rb
107
- * Twitter: https://twitter.com/cyri_
283
+ * Twitter: [https://twitter.com/cyri\_](https://twitter.com/cyri\_)
108
284
 
109
285
  ## Versioning
110
286
 
111
287
  __RSpec clone__ follows [Semantic Versioning 2.0](https://semver.org/).
112
288
 
289
+ ## Special thanks
290
+
291
+ I would like to thank the whole [RSpec team](https://rspec.info/about/) for all their work.
292
+ It's a great framework and it's a pleasure to work with every day.
293
+
294
+ Without RSpec, this clone would not have been possible. ❤️
295
+
113
296
  ## License
114
297
 
115
298
  The [gem](https://rubygems.org/gems/r_spec) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/lib/r_spec.rb CHANGED
@@ -13,13 +13,11 @@
13
13
  module RSpec
14
14
  # Specs are built with this method.
15
15
  #
16
- # @param const [Module] A module to include in block context.
16
+ # @param const [Module, String] A module to include in block context.
17
17
  # @param block [Proc] The block to define the specs.
18
18
  #
19
19
  # @api public
20
20
  def self.describe(const, &block)
21
- raise ::TypeError, const.class.inspect unless const.is_a?(::Module)
22
-
23
21
  DSL.describe(const, &block)
24
22
  end
25
23
  end
data/lib/r_spec/dsl.rb CHANGED
@@ -31,7 +31,7 @@ module RSpec
31
31
  # @param const [Module, #object_id] A module to include in block context.
32
32
  # @param block [Proc] The block to define the specs.
33
33
  def self.describe(const, &block)
34
- desc = Test.const_set("Test#{random_str}", ::Class.new(self))
34
+ desc = Test.const_set(random_test_const_name, ::Class.new(self))
35
35
 
36
36
  if const.is_a?(::Module)
37
37
  desc.define_method(:described_class) { const }
@@ -42,14 +42,15 @@ module RSpec
42
42
  desc
43
43
  end
44
44
 
45
+ # Add `context` to the DSL.
45
46
  singleton_class.send(:alias_method, :context, :describe)
46
47
 
47
48
  # Evaluate an expectation.
48
49
  #
49
50
  # @param block [Proc] An expectation to evaluate.
50
51
  #
51
- # @raise (see ExpectationTarget#result)
52
- # @return (see ExpectationTarget#result)
52
+ # @raise (see ExpectationTarget::Base#result)
53
+ # @return (see ExpectationTarget::Base#result)
53
54
  def self.it(_name = nil, &block)
54
55
  raise ::ArgumentError, "Missing block" unless block
55
56
 
@@ -62,8 +63,10 @@ module RSpec
62
63
  # @private
63
64
  #
64
65
  # @return [Class<DSL>] The class of the example to be tested.
65
- private_class_method def self.example
66
+ def self.example
67
+ # Dynamic creation of an example class.
66
68
  ::Class.new(self) do
69
+ # Include a collection of matchers.
67
70
  include ::Matchi::Helper
68
71
 
69
72
  private
@@ -73,8 +76,8 @@ module RSpec
73
76
  # @param actual [#object_id] The actual value.
74
77
  #
75
78
  # @return [ExpectationTarget] The target of the expectation.
76
- def expect(actual)
77
- ExpectationTarget.new(actual)
79
+ def expect(actual = self.class.superclass, &block)
80
+ ExpectationTarget.call(self.class.superclass, actual, block)
78
81
  end
79
82
 
80
83
  # Wraps the target of an expectation with the subject as actual value.
@@ -84,6 +87,11 @@ module RSpec
84
87
  expect(subject)
85
88
  end
86
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.
87
95
  def log(message)
88
96
  Log.result(message)
89
97
  end
@@ -101,10 +109,12 @@ module RSpec
101
109
 
102
110
  # @private
103
111
  #
104
- # @return [String] A random string.
105
- private_class_method def self.random_str
106
- ::SecureRandom.alphanumeric(5)
112
+ # @return [String] A random constant name for a test class.
113
+ def self.random_test_const_name
114
+ "Test#{::SecureRandom.hex(4).to_i(16)}"
107
115
  end
116
+
117
+ private_class_method :example, :random_test_const_name
108
118
  end
109
119
  end
110
120
 
@@ -1,93 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "expresenter"
4
-
5
3
  module RSpec
6
4
  # Wraps the target of an expectation.
7
- #
8
- # @example
9
- # expect(something) # => ExpectationTarget wrapping something
10
- #
11
- # # used with `to`
12
- # expect(actual).to be(42)
13
- #
14
- # # with `not_to`
15
- # expect(actual).not_to be(4)
16
- #
17
- # @note `ExpectationTarget` is not intended to be instantiated directly by
18
- # users. Use `expect` instead.
19
- class ExpectationTarget
20
- # Instantiate a new expectation target.
21
- #
22
- # @param actual [#object_id] The actual value.
23
- #
24
- # @api private
25
- def initialize(actual)
26
- @actual = actual
27
- end
28
-
29
- # Runs the given expectation, passing if `matcher` returns true.
30
- #
31
- # @example _Absolute requirement_ definition
32
- # expect("foo".upcase).to eq("foo")
33
- #
34
- # @param matcher [#matches?] The matcher.
35
- #
36
- # @raise (see #result)
37
- # @return (see #result)
38
- def to(matcher)
39
- absolute_requirement(matcher: matcher, negate: false)
40
- end
41
-
42
- # Runs the given expectation, passing if `matcher` returns false.
43
- #
44
- # @example _Absolute prohibition_ definition
45
- # expect("foo".size).not_to be(4)
46
- #
47
- # @param (see #to)
48
- #
49
- # @raise (see #result)
50
- # @return (see #result)
51
- def not_to(matcher)
52
- absolute_requirement(matcher: matcher, negate: true)
53
- end
54
-
55
- protected
5
+ module ExpectationTarget
6
+ # @param undefined_value A sentinel value to be able to tell when the user
7
+ # did not pass an argument. We can't use `nil` for that because `nil` is a
8
+ # valid value to pass.
9
+ # @param value [#object_id, nil] An actual value
10
+ # @param block [#call, nil] A code to evaluate.
11
+ #
12
+ # @private
13
+ def self.call(undefined_value, value, block)
14
+ if undefined_value.equal?(value)
15
+ raise ::ArgumentError, "Pass either an argument or a block" unless block
56
16
 
57
- # @param matcher [#matches?] The matcher.
58
- # @param negate [Boolean] Positive or negative assertion?
59
- #
60
- # @raise (see #result)
61
- # @return (see #result)
62
- def absolute_requirement(matcher:, negate:)
63
- result(
64
- matcher: matcher,
65
- negate: negate,
66
- passed: negate ^ matcher.matches? { @actual }
67
- )
68
- end
17
+ Block.new(block)
18
+ else
19
+ raise ::ArgumentError, "Can't pass both an argument and a block" if block
69
20
 
70
- # @param matcher [#matches?] The matcher.
71
- # @param negate [Boolean] Positive or negative assertion?
72
- # @param passed [Boolean] The result of an expectation.
73
- #
74
- # @return [nil] Write a message to STDOUT.
75
- #
76
- # @raise [SystemExit] Terminate execution immediately by calling
77
- # `Kernel.exit(false)` with a failure message written to STDERR.
78
- def result(matcher:, negate:, passed:)
79
- puts " " + ::Expresenter.call(passed).with(
80
- actual: @actual,
81
- error: nil,
82
- expected: matcher.expected,
83
- got: passed,
84
- negate: negate,
85
- valid: passed,
86
- matcher: matcher.class.to_sym,
87
- level: :MUST
88
- ).colored_string
89
- rescue ::Expresenter::Fail => e
90
- abort " #{e.colored_string}"
21
+ Value.new(value)
22
+ end
91
23
  end
92
24
  end
93
25
  end
26
+
27
+ require_relative File.join("expectation_target", "block")
28
+ require_relative File.join("expectation_target", "value")
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "expresenter"
4
+
5
+ module RSpec
6
+ module ExpectationTarget
7
+ # Abstract class.
8
+ #
9
+ # @private
10
+ class Base
11
+ # Runs the given expectation, passing if `matcher` returns true.
12
+ #
13
+ # @example _Absolute requirement_ definition
14
+ # expect { "foo".upcase }.to eq("foo")
15
+ #
16
+ # @param matcher [#matches?] The matcher.
17
+ #
18
+ # @raise (see #result)
19
+ # @return (see #result)
20
+ def to(matcher)
21
+ absolute_requirement(matcher: matcher, negate: false)
22
+ end
23
+
24
+ # Runs the given expectation, passing if `matcher` returns false.
25
+ #
26
+ # @example _Absolute prohibition_ definition
27
+ # expect { "foo".size }.not_to be(4)
28
+ #
29
+ # @param (see #to)
30
+ #
31
+ # @raise (see #result)
32
+ # @return (see #result)
33
+ def not_to(matcher)
34
+ absolute_requirement(matcher: matcher, negate: true)
35
+ end
36
+
37
+ protected
38
+
39
+ # @param actual [#object_id] The actual value.
40
+ # @param error [Exception, nil] Any raised exception.
41
+ # @param got [Boolean, nil] Any returned value.
42
+ # @param matcher [#matches?] The matcher.
43
+ # @param negate [Boolean] The assertion is positive or negative.
44
+ # @param valid [Boolean] The result of an expectation.
45
+ #
46
+ # @return [nil] Write a message to STDOUT.
47
+ #
48
+ # @raise [SystemExit] Terminate execution immediately by calling
49
+ # `Kernel.exit(false)` with a failure message written to STDERR.
50
+ def result(actual:, error:, got:, matcher:, negate:, valid:)
51
+ puts " " + ::Expresenter.call(valid).with(
52
+ actual: actual,
53
+ error: error,
54
+ expected: matcher.expected,
55
+ got: got,
56
+ negate: negate,
57
+ valid: valid,
58
+ matcher: matcher.class.to_sym,
59
+ level: :MUST
60
+ ).colored_string
61
+ rescue ::Expresenter::Fail => e
62
+ abort " #{e.colored_string}"
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spectus/exam"
4
+
5
+ require_relative "base"
6
+
7
+ module RSpec
8
+ module ExpectationTarget
9
+ # Wraps the target of an expectation.
10
+ #
11
+ # @example
12
+ # expect { something } # => ExpectationTarget::Block wrapping something
13
+ #
14
+ # # used with `to`
15
+ # expect { actual }.to be(42)
16
+ #
17
+ # # with `not_to`
18
+ # expect { actual }.not_to be(4)
19
+ #
20
+ # @note `RSpec::ExpectationTarget::Block` is not intended to be instantiated
21
+ # directly by users. Use `expect` instead.
22
+ #
23
+ # @private
24
+ class Block < Base
25
+ # Instantiate a new expectation target.
26
+ #
27
+ # @param block [#call] The code to evaluate.
28
+ #
29
+ # @api private
30
+ def initialize(block)
31
+ super()
32
+
33
+ @callable = block
34
+ end
35
+
36
+ protected
37
+
38
+ # @param matcher [#matches?] The matcher.
39
+ # @param negate [Boolean] Positive or negative assertion?
40
+ #
41
+ # @raise (see Base#result)
42
+ # @return (see Base#result)
43
+ def absolute_requirement(matcher:, negate:)
44
+ exam = ::Spectus::Exam.new(
45
+ callable: @callable,
46
+ isolation: false,
47
+ negate: negate,
48
+ matcher: matcher
49
+ )
50
+
51
+ result(
52
+ actual: exam.actual,
53
+ error: exam.exception,
54
+ got: exam.got,
55
+ matcher: matcher,
56
+ negate: negate,
57
+ valid: exam.valid?
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module RSpec
6
+ module ExpectationTarget
7
+ # Wraps the target of an expectation.
8
+ #
9
+ # @example
10
+ # expect(something) # => ExpectationTarget::Value wrapping something
11
+ #
12
+ # # used with `to`
13
+ # expect(actual).to be(42)
14
+ #
15
+ # # with `not_to`
16
+ # expect(actual).not_to be(4)
17
+ #
18
+ # @note `RSpec::ExpectationTarget::Value` is not intended to be instantiated
19
+ # directly by users. Use `expect` instead.
20
+ #
21
+ # @private
22
+ class Value < Base
23
+ # Instantiate a new expectation target.
24
+ #
25
+ # @param actual [#object_id] The actual value.
26
+ #
27
+ # @api private
28
+ def initialize(actual)
29
+ super()
30
+
31
+ @actual = actual
32
+ end
33
+
34
+ protected
35
+
36
+ # @param matcher [#matches?] The matcher.
37
+ # @param negate [Boolean] Positive or negative assertion?
38
+ #
39
+ # @raise (see Base#result)
40
+ # @return (see Base#result)
41
+ def absolute_requirement(matcher:, negate:)
42
+ valid = negate ^ matcher.matches? { @actual }
43
+
44
+ result(
45
+ actual: @actual,
46
+ error: nil,
47
+ got: valid,
48
+ matcher: matcher,
49
+ negate: negate,
50
+ valid: valid
51
+ )
52
+ end
53
+ end
54
+ end
55
+ 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.beta2
4
+ version: 1.0.0.beta3
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-04 00:00:00.000000000 Z
11
+ date: 2021-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: expresenter
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 1.1.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: spectus
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.3.2
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.3.2
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -164,7 +178,7 @@ dependencies:
164
178
  - - ">="
165
179
  - !ruby/object:Gem::Version
166
180
  version: '0'
167
- description: A minimalist RSpec clone with an emphasis on correctness and simplicity.
181
+ description: A minimalist RSpec clone with all the essentials.
168
182
  email: contact@cyril.email
169
183
  executables: []
170
184
  extensions: []
@@ -175,6 +189,9 @@ files:
175
189
  - lib/r_spec.rb
176
190
  - lib/r_spec/dsl.rb
177
191
  - lib/r_spec/expectation_target.rb
192
+ - lib/r_spec/expectation_target/base.rb
193
+ - lib/r_spec/expectation_target/block.rb
194
+ - lib/r_spec/expectation_target/value.rb
178
195
  - lib/r_spec/log.rb
179
196
  - lib/r_spec/pending.rb
180
197
  - lib/r_spec/test.rb