r_spec 1.0.0.beta2 → 1.0.0.beta3

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: 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