spectus 3.3.3 → 4.0.1

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