spectus 3.3.3 → 4.0.1

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: 99e857c2dbe2b02d961770b40bd9119fa115665105c99f0a6eb813266e9b72be
4
- data.tar.gz: 6f54bf36215f3f662257cffd68e420051f83f6de13d15d9dca60ffb7e89b9c0f
3
+ metadata.gz: ed1222cb49baae653e3e0650224f658c9efe61059d3a91e492db7d7a9c2b9ff4
4
+ data.tar.gz: 6894d5af94f3bafb3164dfa129ee3ced2914fc22edb51968ff005037c0c292be
5
5
  SHA512:
6
- metadata.gz: 5d6b1e7a99bc5d6b52760a8f5fa37f58d485bda7fbf08d9790f78944375d46807366cc9c0232f5f0bb25940b668f89b8a153b78940204ee8e0c54cdc371dacd4
7
- data.tar.gz: 658aaa685fe75ea3fd06c92b0ddca3066efb841cf3c0aa4b252f7d2a9f797ea9f44791ff441bc72bfc76f95fd3f3c532a8b4a1b74e40ea832f6c72ffcbff9b3d
6
+ metadata.gz: 07f4bc04c3c3d02874f52f1f59c9436651933c4830d456ef64895a6d94e2c4c5672e341b416a998400e43fb861e89fa721cc0879af99c89e26e09b1d5f0c7724
7
+ data.tar.gz: 9b7a1da40d5c191f00d4856259a8908aceb87fa73eeaaae6c447448866a73389ba23b7fd9b6b5845b5588e6adac3bd5cb0a1630ddb912752846d0d260c41f95e
data/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # Spectus
2
2
 
3
- [![Build Status](https://api.travis-ci.org/fixrb/spectus.svg?branch=main)][travis]
4
- [![Gem Version](https://badge.fury.io/rb/spectus.svg)][gem]
5
- [![Inline docs](https://inch-ci.org/github/fixrb/spectus.svg?branch=main)][inchpages]
6
- [![Documentation](https://img.shields.io/:yard-docs-38c800.svg)][rubydoc]
3
+ [![Version](https://img.shields.io/github/v/tag/fixrb/spectus?label=Version&logo=github)](https://github.com/fixrb/spectus/releases)
4
+ [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/fixrb/spectus/main)
5
+ [![CI](https://github.com/fixrb/spectus/workflows/CI/badge.svg?branch=main)](https://github.com/fixrb/spectus/actions?query=workflow%3Aci+branch%3Amain)
6
+ [![RuboCop](https://github.com/fixrb/spectus/workflows/RuboCop/badge.svg?branch=main)](https://github.com/fixrb/spectus/actions?query=workflow%3Arubocop+branch%3Amain)
7
+ [![License](https://img.shields.io/github/license/fixrb/spectus?label=License&logo=github)](https://github.com/fixrb/spectus/raw/main/LICENSE.md)
7
8
 
8
- > Expectation library with [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)'s requirement levels 🚥
9
+ > Expectation library with [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) requirement levels 🚥
9
10
 
10
11
  ## Installation
11
12
 
@@ -29,72 +30,91 @@ gem install spectus
29
30
 
30
31
  ## Usage
31
32
 
32
- To begin with, let's include __Spectus__:
33
+ The __Spectus__ library is basically a module defining methods that can be used to qualify expectations in specifications.
34
+
35
+ To make __Spectus__ available:
33
36
 
34
37
  ```ruby
35
- include Spectus
38
+ require "spectus"
36
39
  ```
37
40
 
38
- ### Absolute Requirement
41
+ For convenience, we will also instantiate some matchers from the [Matchi library](https://github.com/fixrb/matchi):
39
42
 
40
- Given the "`ルビー`" object, when it receives `valid_encoding?` method, then it **MUST** be `true`:
43
+ ```sh
44
+ gem install matchi
45
+ ```
41
46
 
42
47
  ```ruby
43
- it { "ルビー".valid_encoding? }.MUST be_true
44
- # => Spectus::Result::Pass(actual: true, error: nil, expected: nil, got: true, matcher: :be_true, negate: false, level: :MUST, valid: true)
48
+ require "matchi/helper"
49
+
50
+ include Matchi::Helper
45
51
  ```
46
52
 
47
- The result of the test shows that the spec passed.
53
+ All examples here assume that this has been done.
48
54
 
49
- ### Absolute Prohibition
55
+ ### Absolute Requirement
50
56
 
51
- Given the "`foo`" object, when it receives `length` method, then it **MUST NOT** raise the `NoMethodError` exception:
57
+ There's only one bat:
52
58
 
53
59
  ```ruby
54
- it { "foo".length }.MUST_NOT raise_exception NoMethodError
55
- # => Spectus::Result::Pass(actual: 3, error: nil, expected: NoMethodError, got: true, matcher: :raise_exception, negate: true, level: :MUST, valid: true)
60
+ definition = Spectus.must equal 1
61
+ definition.call { "🦇".size }
62
+ # => Expresenter::Pass(actual: 1, error: nil, expected: 1, got: true, matcher: :equal, negate: false, level: :MUST
56
63
  ```
57
64
 
58
- The result of the test shows that the spec passed.
65
+ ### Absolute Prohibition
66
+
67
+ The true from the false:
68
+
69
+ ```ruby
70
+ definition = Spectus.must_not be_true
71
+ definition.call { false }
72
+ # => Expresenter::Pass(actual: false, error: nil, expected: nil, got: true, matcher: :be_true, negate: true, level: :MUST
73
+ ```
59
74
 
60
75
  ### Recommended
61
76
 
62
- Given the `BasicObject` object, when it receives `superclass` method, then it **SHOULD** return the explicit blank class `NilClass`:
77
+ A well-known joke. An addition of `0.1` and `0.2` is deadly precise:
63
78
 
64
79
  ```ruby
65
- it { BasicObject.superclass }.SHOULD equal NilClass
66
- # => Spectus::Result::Pass(actual: nil, error: nil, expected: NilClass, got: false, matcher: :equal, negate: false, level: :SHOULD, valid: false)
80
+ definition = Spectus.should equal 0.3
81
+ definition.call { 0.1 + 0.2 }
82
+ # => Expresenter::Pass(actual: 0.30000000000000004, error: nil, expected: 0.3, got: false, matcher: :equal, negate: false, level: :SHOULD
67
83
  ```
68
84
 
69
- Instead of the expected `NilClass` class, its sole instance (which is `nil`) was returned.
70
- However, because there isn't any exception, the result of the test shows that the spec passed.
71
-
72
85
  ### Not Recommended
73
86
 
74
- Given the "`1`" object, when it receives `+(1)` method, then it **SHOULD NOT** return the "`11`" value:
87
+ The situation should still be under control:
75
88
 
76
89
  ```ruby
77
- it { "1" + 1 }.SHOULD_NOT eql "11"
78
- # raise Spectus::Result::Fail(actual: nil, error: #<TypeError: no implicit conversion of Integer into String>, expected: "11", got: nil, matcher: :eql, negate: true, level: :SHOULD, valid: false)
90
+ definition = Spectus.should_not raise_exception SystemExit
91
+ definition.call { BOOM }
79
92
  ```
80
93
 
81
- There was a `TypeError` exception, the result of the test shows that the spec failed.
94
+ ```txt
95
+ Traceback (most recent call last):
96
+ 6: from /Users/cyril/.rbenv/versions/2.7.3/bin/irb:23:in `<main>'
97
+ 5: from /Users/cyril/.rbenv/versions/2.7.3/bin/irb:23:in `load'
98
+ 4: from /Users/cyril/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
99
+ 3: from (irb):11
100
+ 2: from /Users/cyril/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/spectus-4.0.0/lib/spectus/requirement/base.rb:32:in `call'
101
+ 1: from /Users/cyril/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/expresenter-1.3.0/lib/expresenter/fail.rb:25:in `with'
102
+ Expresenter::Fail (NameError: uninitialized constant BOOM.)
103
+ ```
82
104
 
83
105
  ### Optional
84
106
 
85
- Given the "`foo`" object, when it receives `blank?` method, then it **MAY** be `false`:
107
+ An empty array is blank, right?
86
108
 
87
109
  ```ruby
88
- it { "foo".blank? }.MAY be_false
89
- # => Spectus::Result::Pass(actual: nil, error: #<NoMethodError: undefined method `blank?' for "foo":String>, expected: nil, got: nil, matcher: :be_false, negate: false, level: :MAY, valid: false)
110
+ definition = Spectus.may be_true
111
+ definition.call { [].blank? }
112
+ # => Expresenter::Pass(actual: nil, error: #<NoMethodError: undefined method `blank?' for []:Array>, expected: nil, got: nil, matcher: :be_true, negate: false, level: :MAY
90
113
  ```
91
114
 
92
- The optional `blank?` method is not implemented (unlike in [Ruby on Rails](https://api.rubyonrails.org/classes/Object.html#method-i-blank-3F), for instance), so the result of the test shows that the spec passed.
93
-
94
- ### More Examples
115
+ Damn, I forgot to load activesupport. 🤦‍♂️
95
116
 
96
- A full list of unit tests can be viewed (and executed) here:
97
- [./test.rb](https://github.com/fixrb/spectus/blob/main/test.rb)
117
+ That said, the test is passing due to the _not-implemented-like_ raised exception: `NoMethodError`.
98
118
 
99
119
  ## Code Isolation
100
120
 
@@ -107,20 +127,24 @@ Because they may or may not be desired, each requirement level has 2 versions:
107
127
  Example of test without isolation:
108
128
 
109
129
  ```ruby
110
- include Spectus
111
130
  greeting = "Hello, world!"
112
- it { greeting.gsub!("world", "Alice") }.MUST eql "Hello, Alice!"
113
- # => Spectus::Result::Pass(actual: "Hello, Alice!", error: nil, expected: "Hello, Alice!", got: true, matcher: :eql, negate: false, level: :MUST, valid: true)
131
+
132
+ definition = Spectus.must eql "Hello, Alice!"
133
+ definition.call { greeting.gsub!("world", "Alice") }
134
+ # => Expresenter::Pass(actual: "Hello, Alice!", error: nil, expected: "Hello, Alice!", got: true, matcher: :eql, negate: false, level: :MUST
135
+
114
136
  greeting # => "Hello, Alice!"
115
137
  ```
116
138
 
117
139
  Example of test in isolation:
118
140
 
119
141
  ```ruby
120
- include Spectus
121
142
  greeting = "Hello, world!"
122
- it { greeting.gsub!("world", "Alice") }.MUST! eql "Hello, Alice!"
123
- # => Spectus::Result::Pass(actual: "Hello, Alice!", error: nil, expected: "Hello, Alice!", got: true, matcher: :eql, negate: false, level: :MUST, valid: true)
143
+
144
+ definition = Spectus.must! eql "Hello, Alice!"
145
+ definition.call { greeting.gsub!("world", "Alice") }
146
+ # => Expresenter::Pass(actual: "Hello, Alice!", error: nil, expected: "Hello, Alice!", got: true, matcher: :eql, negate: false, level: :MUST
147
+
124
148
  greeting # => "Hello, world!"
125
149
  ```
126
150
 
@@ -128,6 +152,7 @@ greeting # => "Hello, world!"
128
152
 
129
153
  * Home page: https://github.com/fixrb/spectus
130
154
  * Bugs/issues: https://github.com/fixrb/spectus/issues
155
+ * Blog post: https://batman.buzz/a-spectus-tutorial-expectations-with-rfc-2119-compliance-1fc769861c1
131
156
 
132
157
  ## Versioning
133
158
 
@@ -135,7 +160,7 @@ __Spectus__ follows [Semantic Versioning 2.0](https://semver.org/).
135
160
 
136
161
  ## License
137
162
 
138
- The [gem](https://rubygems.org/gems/spectus) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
163
+ The [gem](https://rubygems.org/gems/spectus) is available as open source under the terms of the [MIT License](https://github.com/fixrb/spectus/raw/main/LICENSE.md).
139
164
 
140
165
  ***
141
166
 
@@ -145,8 +170,3 @@ The [gem](https://rubygems.org/gems/spectus) is available as open source under t
145
170
  src="https://github.com/fixrb/spectus/raw/main/img/sashite.png"
146
171
  alt="Sashite" /></a>
147
172
  </p>
148
-
149
- [gem]: https://rubygems.org/gems/spectus
150
- [travis]: https://travis-ci.org/fixrb/spectus
151
- [inchpages]: https://inch-ci.org/github/fixrb/spectus
152
- [rubydoc]: https://rubydoc.info/gems/spectus/frames
data/lib/spectus.rb CHANGED
@@ -1,26 +1,226 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "matchi/helper"
3
+ require_relative File.join("spectus", "requirement")
4
4
 
5
5
  # Namespace for the Spectus library.
6
6
  #
7
- # @example It MUST equal 42.
8
- # require 'spectus'
9
- # it { 42 }.MUST equal 42 # => #<Spectus::Result::Pass...>
7
+ # This module defines methods that can be used to qualify expectations in
8
+ # specifications.
10
9
  module Spectus
11
- include ::Matchi::Helper
10
+ # This method mean that the definition is an absolute requirement of the specification.
11
+ #
12
+ # @example An absolute requirement definition
13
+ # require "spectus"
14
+ # require "matchi/helper"
15
+ #
16
+ # include Matchi::Helper
17
+ #
18
+ # Spectus.must eql "FOO"
19
+ # # => #<MUST Matchi::Matcher::Eql("FOO") isolate=false negate=false>
20
+ #
21
+ # @param matcher [#matches?] The matcher.
22
+ #
23
+ # @return [Requirement::Required] An absolute requirement level instance.
24
+ #
25
+ # @api public
26
+ def self.must(matcher)
27
+ Requirement::Required.new(
28
+ isolate: false,
29
+ negate: false,
30
+ matcher: matcher
31
+ )
32
+ end
12
33
 
13
- # Expectations are built with this method.
34
+ # @example An absolute requirement definition with isolation
35
+ # require "spectus"
36
+ # require "matchi/helper"
14
37
  #
15
- # @example An _absolute requirement_ definition.
16
- # it { 42 }.MUST equal 42 # => #<Spectus::Result::Pass...>
38
+ # include Matchi::Helper
17
39
  #
18
- # @param input [Proc] The code to test.
40
+ # Spectus.must! eql "FOO"
41
+ # # => #<MUST Matchi::Matcher::Eql("FOO") isolate=true negate=false>
19
42
  #
20
- # @return [ExpectationTarget] The expectation target.
21
- def it(&input)
22
- ExpectationTarget.new(&input)
43
+ # @see must
44
+ def self.must!(matcher)
45
+ Requirement::Required.new(
46
+ isolate: true,
47
+ negate: false,
48
+ matcher: matcher
49
+ )
50
+ end
51
+
52
+ # This method mean that the definition is an absolute prohibition of the specification.
53
+ #
54
+ # @example An absolute prohibition definition
55
+ # require "spectus"
56
+ # require "matchi/helper"
57
+ #
58
+ # include Matchi::Helper
59
+ #
60
+ # Spectus.must_not equal 42
61
+ # # => #<MUST Matchi::Matcher::Equal(42) isolate=false negate=true>
62
+ #
63
+ # @param matcher [#matches?] The matcher.
64
+ #
65
+ # @return [Requirement::Required] An absolute prohibition level instance.
66
+ def self.must_not(matcher)
67
+ Requirement::Required.new(
68
+ isolate: false,
69
+ negate: true,
70
+ matcher: matcher
71
+ )
72
+ end
73
+
74
+ # @example An absolute prohibition definition with isolation
75
+ # require "spectus"
76
+ # require "matchi/helper"
77
+ #
78
+ # include Matchi::Helper
79
+ #
80
+ # Spectus.must_not! equal 42
81
+ # # => #<MUST Matchi::Matcher::Equal(42) isolate=true negate=true>
82
+ #
83
+ # @see must_not
84
+ def self.must_not!(matcher)
85
+ Requirement::Required.new(
86
+ isolate: true,
87
+ negate: true,
88
+ matcher: matcher
89
+ )
90
+ end
91
+
92
+ # This method mean that there may exist valid reasons in particular
93
+ # circumstances to ignore a particular item, but the full implications must be
94
+ # understood and carefully weighed before choosing a different course.
95
+ #
96
+ # @example A recommended definition
97
+ # require "spectus"
98
+ # require "matchi/helper"
99
+ #
100
+ # include Matchi::Helper
101
+ #
102
+ # Spectus.should equal true
103
+ # # => #<SHOULD Matchi::Matcher::Equal(true) isolate=false negate=false>
104
+ #
105
+ # @param matcher [#matches?] The matcher.
106
+ #
107
+ # @return [Requirement::Recommended] A recommended requirement level instance.
108
+ def self.should(matcher)
109
+ Requirement::Recommended.new(
110
+ isolate: false,
111
+ negate: false,
112
+ matcher: matcher
113
+ )
114
+ end
115
+
116
+ # @example A recommended definition with isolation
117
+ # require "spectus"
118
+ # require "matchi/helper"
119
+ #
120
+ # include Matchi::Helper
121
+ #
122
+ # Spectus.should! equal true
123
+ # # => #<SHOULD Matchi::Matcher::Equal(true) isolate=true negate=false>
124
+ #
125
+ # @see should
126
+ def self.should!(matcher)
127
+ Requirement::Recommended.new(
128
+ isolate: true,
129
+ negate: false,
130
+ matcher: matcher
131
+ )
132
+ end
133
+
134
+ # This method mean that there may exist valid reasons in particular
135
+ # circumstances when the particular behavior is acceptable or even useful, but
136
+ # the full implications should be understood and the case carefully weighed
137
+ # before implementing any behavior described with this label.
138
+ #
139
+ # @example A not recommended definition
140
+ # require "spectus"
141
+ # require "matchi/helper"
142
+ #
143
+ # include Matchi::Helper
144
+ #
145
+ # Spectus.should_not raise_exception NoMethodError
146
+ # # => #<SHOULD Matchi::Matcher::RaiseException(NoMethodError) isolate=false negate=true>
147
+ #
148
+ # @param matcher [#matches?] The matcher.
149
+ #
150
+ # @return [Requirement::Recommended] A not recommended requirement level
151
+ # instance.
152
+ def self.should_not(matcher)
153
+ Requirement::Recommended.new(
154
+ isolate: false,
155
+ negate: true,
156
+ matcher: matcher
157
+ )
158
+ end
159
+
160
+ # @example A not recommended definition with isolation
161
+ # require "spectus"
162
+ # require "matchi/helper"
163
+ #
164
+ # include Matchi::Helper
165
+ #
166
+ # Spectus.should_not! raise_exception NoMethodError
167
+ # # => #<SHOULD Matchi::Matcher::RaiseException(NoMethodError) isolate=true negate=true>
168
+ #
169
+ # @see should_not
170
+ def self.should_not!(matcher)
171
+ Requirement::Recommended.new(
172
+ isolate: true,
173
+ negate: true,
174
+ matcher: matcher
175
+ )
176
+ end
177
+
178
+ # This method mean that an item is truly optional.
179
+ # One vendor may choose to include the item because a particular marketplace
180
+ # requires it or because the vendor feels that it enhances the product while
181
+ # another vendor may omit the same item. An implementation which does not
182
+ # include a particular option must be prepared to interoperate with another
183
+ # implementation which does include the option, though perhaps with reduced
184
+ # functionality. In the same vein an implementation which does include a
185
+ # particular option must be prepared to interoperate with another
186
+ # implementation which does not include the option (except, of course, for the
187
+ # feature the option provides).
188
+ #
189
+ # @example An optional definition
190
+ # require "spectus"
191
+ # require "matchi/helper"
192
+ #
193
+ # include Matchi::Helper
194
+ #
195
+ # Spectus.may match /^foo$/
196
+ # # => #<MAY Matchi::Matcher::Match(/^foo$/) isolate=false negate=false>
197
+ #
198
+ # @param matcher [#matches?] The matcher.
199
+ #
200
+ # @return [Requirement::Optional] An optional requirement level instance.
201
+ def self.may(matcher)
202
+ Requirement::Optional.new(
203
+ isolate: false,
204
+ negate: false,
205
+ matcher: matcher
206
+ )
23
207
  end
24
- end
25
208
 
26
- require_relative File.join("spectus", "expectation_target")
209
+ # @example An optional definition with isolation
210
+ # require "spectus"
211
+ # require "matchi/helper"
212
+ #
213
+ # include Matchi::Helper
214
+ #
215
+ # Spectus.may! match /^foo$/
216
+ # # => #<MAY Matchi::Matcher::Match(/^foo$/) isolate=true negate=false>
217
+ #
218
+ # @see may
219
+ def self.may!(matcher)
220
+ Requirement::Optional.new(
221
+ isolate: true,
222
+ negate: false,
223
+ matcher: matcher
224
+ )
225
+ end
226
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spectus
4
+ # Namespace for the results.
5
+ #
6
+ # @api private
7
+ module Requirement
8
+ end
9
+ end
10
+
11
+ require_relative File.join("requirement", "required")
12
+ require_relative File.join("requirement", "recommended")
13
+ require_relative File.join("requirement", "optional")
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "expresenter"
4
+ require "test_tube"
5
+
6
+ module Spectus
7
+ # Namespace for the requirement levels.
8
+ module Requirement
9
+ # Requirement level's base class.
10
+ class Base
11
+ # Initialize the requirement level class.
12
+ #
13
+ # @param isolate [Boolean] Compute actual in a subprocess.
14
+ # @param matcher [#matches?] The matcher.
15
+ # @param negate [Boolean] Invert the matcher or not.
16
+ def initialize(isolate:, matcher:, negate:)
17
+ @isolate = isolate
18
+ @matcher = matcher
19
+ @negate = negate
20
+ end
21
+
22
+ # Test result.
23
+ #
24
+ # @raise [::Expresenter::Fail] A failed spec exception.
25
+ # @return [::Expresenter::Pass] A passed spec instance.
26
+ #
27
+ # @see https://github.com/fixrb/expresenter
28
+ #
29
+ # @api public
30
+ def call(&block)
31
+ test = ::TestTube.invoke(isolate: @isolate, matcher: @matcher, negate: @negate, &block)
32
+
33
+ ::Expresenter.call(passed?(test)).with(
34
+ actual: test.actual,
35
+ error: test.error,
36
+ expected: @matcher.expected,
37
+ got: test.got,
38
+ level: self.class.level,
39
+ matcher: @matcher.class.to_sym,
40
+ negate: @negate
41
+ )
42
+ end
43
+
44
+ # :nocov:
45
+
46
+ # A string containing a human-readable representation of the definition.
47
+ #
48
+ # @example The human-readable representation of an absolute requirement.
49
+ # require "spectus"
50
+ # require "matchi/helper"
51
+ #
52
+ # include Matchi::Helper
53
+ #
54
+ # definition = Spectus.must equal 1
55
+ # definition.inspect
56
+ # # => #<MUST Matchi::Matcher::Equal(1) isolate=false negate=false>
57
+ #
58
+ # @return [String] The human-readable representation of the definition.
59
+ #
60
+ # @api public
61
+ def inspect
62
+ "#<#{self.class.level} #{@matcher.inspect} isolate=#{@isolate} negate=#{@negate}>"
63
+ end
64
+
65
+ # :nocov:
66
+
67
+ private
68
+
69
+ # Code experiment result.
70
+ #
71
+ # @param test [::TestTube::Base] The state of the experiment.
72
+ #
73
+ # @see https://github.com/fixrb/test_tube
74
+ #
75
+ # @return [Boolean] The result of the test (passed or failed).
76
+ def passed?(test)
77
+ test.got.equal?(true)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Spectus
6
+ module Requirement
7
+ # Optional requirement level.
8
+ class Optional < Base
9
+ # Key word for use in RFCs to indicate requirement levels.
10
+ #
11
+ # @return [Symbol] The requirement level.
12
+ def self.level
13
+ :MAY
14
+ end
15
+
16
+ private
17
+
18
+ # Code experiment result.
19
+ #
20
+ # @param (see Base#passed?)
21
+ #
22
+ # @return (see Base#passed?)
23
+ def passed?(test)
24
+ super || test.error.is_a?(::NoMethodError)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Spectus
6
+ module Requirement
7
+ # Recommended and not recommended requirement levels.
8
+ class Recommended < Base
9
+ # Key word for use in RFCs to indicate requirement levels.
10
+ #
11
+ # @return [Symbol] The requirement level.
12
+ def self.level
13
+ :SHOULD
14
+ end
15
+
16
+ private
17
+
18
+ # Code experiment result.
19
+ #
20
+ # @param (see Base#passed?)
21
+ #
22
+ # @return (see Base#passed?)
23
+ def passed?(test)
24
+ super || test.error.nil?
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Spectus
6
+ module Requirement
7
+ # Absolute requirement and absolute prohibition levels.
8
+ class Required < Base
9
+ # Key word for use in RFCs to indicate requirement levels.
10
+ #
11
+ # @return [Symbol] The requirement level.
12
+ def self.level
13
+ :MUST
14
+ end
15
+ end
16
+ end
17
+ end
metadata CHANGED
@@ -1,59 +1,59 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spectus
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.3
4
+ version: 4.0.1
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-10 00:00:00.000000000 Z
11
+ date: 2021-07-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: defi
14
+ name: expresenter
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.0.5
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: 2.0.5
26
+ version: 1.3.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: expresenter
28
+ name: test_tube
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.2.1
33
+ version: 2.1.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.2.1
40
+ version: 2.1.0
41
41
  - !ruby/object:Gem::Dependency
42
- name: matchi
42
+ name: brutal
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 2.0.2
48
- type: :runtime
47
+ version: '0'
48
+ type: :development
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: 2.0.2
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: brutal
56
+ name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: bundler
70
+ name: matchi
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -187,15 +187,11 @@ files:
187
187
  - LICENSE.md
188
188
  - README.md
189
189
  - lib/spectus.rb
190
- - lib/spectus/exam.rb
191
- - lib/spectus/expectation_target.rb
192
- - lib/spectus/requirement_level/base.rb
193
- - lib/spectus/requirement_level/may.rb
194
- - lib/spectus/requirement_level/must.rb
195
- - lib/spectus/requirement_level/should.rb
196
- - lib/spectus/result.rb
197
- - lib/spectus/result/fail.rb
198
- - lib/spectus/result/pass.rb
190
+ - lib/spectus/requirement.rb
191
+ - lib/spectus/requirement/base.rb
192
+ - lib/spectus/requirement/optional.rb
193
+ - lib/spectus/requirement/recommended.rb
194
+ - lib/spectus/requirement/required.rb
199
195
  homepage: https://github.com/fixrb/spectus
200
196
  licenses:
201
197
  - MIT
data/lib/spectus/exam.rb DELETED
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "defi"
4
-
5
- module Spectus
6
- # This class evaluate the expectation with the passed block.
7
- class Exam
8
- # Execute the untested code from the passed block against the matcher.
9
- #
10
- # rubocop:disable Lint/RescueException
11
- #
12
- # @param callable [#call] The callable object to test.
13
- # @param isolation [Boolean] Compute actual in isolation?
14
- # @param negate [Boolean] Positive or negative assertion?
15
- # @param matcher [#matches?] The matcher.
16
- def initialize(callable:, isolation:, negate:, matcher:)
17
- @got = negate ^ matcher.matches? do
18
- value = if isolation
19
- send_call.to!(callable)
20
- else
21
- send_call.to(callable)
22
- end
23
-
24
- @actual = value.object
25
-
26
- value.call
27
- end
28
- rescue ::Exception => e
29
- @actual = nil
30
- @exception = e
31
- end
32
- # rubocop:enable Lint/RescueException
33
-
34
- # @return [#object_id] The actual value.
35
- attr_reader :actual
36
-
37
- # @return [Exception, nil] An exception.
38
- attr_reader :exception
39
-
40
- # @return [Boolean, nil] Report to the spec requirement level if the
41
- # expectation is true or false.
42
- attr_reader :got
43
-
44
- # @return [Defi::Challenge] The challenge for the callable object.
45
- def send_call
46
- ::Defi.send(:call)
47
- end
48
-
49
- # Report to the spec requirement level if the test pass or fail.
50
- #
51
- # @return [Boolean] Report if the test pass or fail?
52
- def valid?
53
- exception.nil? ? got : false
54
- end
55
- end
56
- end
@@ -1,202 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Spectus
4
- # Wraps the target of an expectation.
5
- #
6
- # @example
7
- # it { actual value } # => ExpectationTarget wrapping the block
8
- class ExpectationTarget
9
- # Create a new expectation target
10
- #
11
- # @param callable [Proc] The object to test.
12
- def initialize(&callable)
13
- @callable = callable
14
- end
15
-
16
- # rubocop:disable Naming/MethodName
17
-
18
- # This word, or the terms "REQUIRED" or "SHALL", mean that the
19
- # definition is an absolute requirement of the specification.
20
- #
21
- # @example _Absolute requirement_ definition
22
- # it { "foo".upcase }.MUST eql 'FOO'
23
- #
24
- # @param matcher [#matches?] The matcher.
25
- #
26
- # @return [Spectus::Result::Fail, Spectus::Result::Pass] Report if the spec
27
- # pass or fail.
28
- def MUST(matcher)
29
- RequirementLevel::Must.new(
30
- callable: callable,
31
- isolation: false,
32
- negate: false,
33
- matcher: matcher
34
- ).call
35
- end
36
-
37
- # @example _Absolute requirement_ definition with isolation
38
- # it { "foo".upcase }.MUST! eql 'FOO'
39
- #
40
- # @see MUST
41
- def MUST!(matcher)
42
- RequirementLevel::Must.new(
43
- callable: callable,
44
- isolation: true,
45
- negate: false,
46
- matcher: matcher
47
- ).call
48
- end
49
-
50
- # This phrase, or the phrase "SHALL NOT", mean that the
51
- # definition is an absolute prohibition of the specification.
52
- #
53
- # @example _Absolute prohibition_ definition
54
- # it { "foo".size }.MUST_NOT equal 42
55
- #
56
- # @param matcher [#matches?] The matcher.
57
- #
58
- # @return [Spectus::Result::Fail, Spectus::Result::Pass] Report if the spec
59
- # pass or fail.
60
- def MUST_NOT(matcher)
61
- RequirementLevel::Must.new(
62
- callable: callable,
63
- isolation: false,
64
- negate: true,
65
- matcher: matcher
66
- ).call
67
- end
68
-
69
- # @example _Absolute prohibition_ definition with isolation
70
- # it { "foo".size }.MUST_NOT! equal 42
71
- #
72
- # @see MUST_NOT
73
- def MUST_NOT!(matcher)
74
- RequirementLevel::Must.new(
75
- callable: callable,
76
- isolation: true,
77
- negate: true,
78
- matcher: matcher
79
- ).call
80
- end
81
-
82
- # This word, or the adjective "RECOMMENDED", mean that there
83
- # may exist valid reasons in particular circumstances to ignore a
84
- # particular item, but the full implications must be understood and
85
- # carefully weighed before choosing a different course.
86
- #
87
- # @example _Recommended_ definition
88
- # it { "foo".valid_encoding? }.SHOULD equal true
89
- #
90
- # @param matcher [#matches?] The matcher.
91
- #
92
- # @return [Spectus::Result::Fail, Spectus::Result::Pass] Report if the spec
93
- # pass or fail.
94
- def SHOULD(matcher)
95
- RequirementLevel::Should.new(
96
- callable: callable,
97
- isolation: false,
98
- negate: false,
99
- matcher: matcher
100
- ).call
101
- end
102
-
103
- # @example _Recommended_ definition with isolation
104
- # it { "foo".valid_encoding? }.SHOULD! equal true
105
- #
106
- # @see SHOULD
107
- def SHOULD!(matcher)
108
- RequirementLevel::Should.new(
109
- callable: callable,
110
- isolation: true,
111
- negate: false,
112
- matcher: matcher
113
- ).call
114
- end
115
-
116
- # This phrase, or the phrase "NOT RECOMMENDED" mean that
117
- # there may exist valid reasons in particular circumstances when the
118
- # particular behavior is acceptable or even useful, but the full
119
- # implications should be understood and the case carefully weighed
120
- # before implementing any behavior described with this label.
121
- #
122
- # @example _Not recommended_ definition
123
- # it { "".blank? }.SHOULD_NOT raise_exception NoMethodError
124
- #
125
- # @param matcher [#matches?] The matcher.
126
- #
127
- # @return [Spectus::Result::Fail, Spectus::Result::Pass] Report if the spec
128
- # pass or fail.
129
- def SHOULD_NOT(matcher)
130
- RequirementLevel::Should.new(
131
- callable: callable,
132
- isolation: false,
133
- negate: true,
134
- matcher: matcher
135
- ).call
136
- end
137
-
138
- # @example _Not recommended_ definition with isolation
139
- # it { "".blank? }.SHOULD_NOT! raise_exception NoMethodError
140
- #
141
- # @see SHOULD_NOT
142
- def SHOULD_NOT!(matcher)
143
- RequirementLevel::Should.new(
144
- callable: callable,
145
- isolation: true,
146
- negate: true,
147
- matcher: matcher
148
- ).call
149
- end
150
-
151
- # This word, or the adjective "OPTIONAL", mean that an item is
152
- # truly optional. One vendor may choose to include the item because a
153
- # particular marketplace requires it or because the vendor feels that
154
- # it enhances the product while another vendor may omit the same item.
155
- # An implementation which does not include a particular option MUST be
156
- # prepared to interoperate with another implementation which does
157
- # include the option, though perhaps with reduced functionality. In the
158
- # same vein an implementation which does include a particular option
159
- # MUST be prepared to interoperate with another implementation which
160
- # does not include the option (except, of course, for the feature the
161
- # option provides.)
162
- #
163
- # @example _Optional_ definition
164
- # it { "foo".bar }.MAY match /^foo$/
165
- #
166
- # @param matcher [#matches?] The matcher.
167
- #
168
- # @return [Spectus::Result::Fail, Spectus::Result::Pass] Report if the spec pass or fail.
169
- def MAY(matcher)
170
- RequirementLevel::May.new(
171
- callable: callable,
172
- isolation: false,
173
- negate: false,
174
- matcher: matcher
175
- ).call
176
- end
177
-
178
- # @example _Optional_ definition with isolation
179
- # it { "foo".bar }.MAY! match /^foo$/
180
- #
181
- # @see MAY
182
- def MAY!(matcher)
183
- RequirementLevel::May.new(
184
- callable: callable,
185
- isolation: true,
186
- negate: false,
187
- matcher: matcher
188
- ).call
189
- end
190
-
191
- # rubocop:enable Naming/MethodName
192
-
193
- protected
194
-
195
- # @return [#call] The callable object to test.
196
- attr_reader :callable
197
- end
198
- end
199
-
200
- require_relative File.join("requirement_level", "must")
201
- require_relative File.join("requirement_level", "should")
202
- require_relative File.join("requirement_level", "may")
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Spectus
4
- # Namespace for the requirement levels.
5
- module RequirementLevel
6
- # Requirement level's base class.
7
- class Base
8
- # Initialize the requirement level class.
9
- #
10
- # @param callable [#call] The callable object to test.
11
- # @param isolation [Boolean] Compute actual in isolation?
12
- # @param negate [Boolean] Positive or negative assertion?
13
- # @param matcher [#matches?] The matcher.
14
- def initialize(callable:, isolation:, negate:, matcher:)
15
- @negate = negate
16
- @matcher = matcher
17
-
18
- @exam = Exam.new(
19
- callable: callable,
20
- isolation: isolation,
21
- negate: negate,
22
- matcher: matcher
23
- )
24
- end
25
-
26
- # @return [#Exam] The exam.
27
- attr_reader :exam
28
-
29
- # @return [#matches?] The matcher that performed a boolean comparison
30
- # between the actual value and the expected value.
31
- attr_reader :matcher
32
-
33
- # The result of the expectation.
34
- #
35
- # @raise [Spectus::Result::Fail] The expectation is `false`.
36
- # @return [Spectus::Result::Pass] The expectation is `true`.
37
- def call
38
- Result.call(pass?).with(
39
- actual: exam.actual,
40
- error: exam.exception,
41
- expected: matcher.expected,
42
- got: exam.got,
43
- negate: negate?,
44
- valid: exam.valid?,
45
- matcher: matcher.class.to_sym,
46
- level: level
47
- )
48
- end
49
-
50
- protected
51
-
52
- # @return [Symbol] The requirement level.
53
- def level
54
- self.class.name.split("::").fetch(-1).upcase.to_sym
55
- end
56
-
57
- # @note The boolean comparison between the actual value and the expected
58
- # value can be evaluated to a negative assertion.
59
- #
60
- # @return [Boolean] Positive or negative assertion?
61
- def negate?
62
- @negate
63
- end
64
- end
65
- end
66
- end
67
-
68
- require_relative File.join("..", "exam")
69
- require_relative File.join("..", "result")
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "must"
4
-
5
- module Spectus
6
- module RequirementLevel
7
- # May requirement level's class.
8
- class May < Must
9
- # Evaluate the expectation.
10
- #
11
- # @return [Boolean] Report if the low expectation pass or fail?
12
- def pass?
13
- super || exam.exception.is_a?(::NoMethodError)
14
- end
15
- end
16
- end
17
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base"
4
-
5
- module Spectus
6
- module RequirementLevel
7
- # Must requirement level's class.
8
- class Must < Base
9
- # Evaluate the expectation.
10
- #
11
- # @return [Boolean] Report if the high expectation pass or fail?
12
- def pass?
13
- exam.valid?
14
- end
15
- end
16
- end
17
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "must"
4
-
5
- module Spectus
6
- module RequirementLevel
7
- # Should requirement level's class.
8
- class Should < Must
9
- # Evaluate the expectation.
10
- #
11
- # @return [Boolean] Report if the medium expectation pass or fail?
12
- def pass?
13
- super || exam.exception.nil?
14
- end
15
- end
16
- end
17
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Spectus
4
- # Namespace for the results.
5
- module Result
6
- # @param is_passed [Boolean] The value of an assertion.
7
- # @return [Class<Spectus::Result::Pass>, Class<Spectus::Result::Fail>] The
8
- # class of the result.
9
- # @example Get the pass class result.
10
- # call(true) # => Pass
11
- def self.call(is_passed)
12
- is_passed ? Pass : Fail
13
- end
14
- end
15
- end
16
-
17
- require_relative File.join("result", "fail")
18
- require_relative File.join("result", "pass")
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "expresenter/fail"
4
-
5
- module Spectus
6
- module Result
7
- # The class that is responsible for reporting that the expectation is false.
8
- class Fail < ::Expresenter::Fail
9
- end
10
- end
11
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "expresenter/pass"
4
-
5
- module Spectus
6
- module Result
7
- # The class that is responsible for reporting that the expectation is true.
8
- class Pass < ::Expresenter::Pass
9
- end
10
- end
11
- end