expresenter 1.4.1 → 1.5.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 +4 -4
- data/LICENSE.md +1 -1
- data/README.md +119 -116
- data/lib/expresenter/common.rb +110 -48
- data/lib/expresenter/fail.rb +103 -33
- data/lib/expresenter/pass.rb +95 -35
- data/lib/expresenter.rb +49 -6
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6701ed87c0d50891bb8804abc202aa3a2579d34f658b2d8b59f2b7caec76228a
|
4
|
+
data.tar.gz: f9d618cddd0aeacb36667da36816a759ff768aadb3e0f401f893baecf1306a2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: edbc7c35fe288a72bf3160810a5acf87675785e49f6f508ceb60a146ad74aa6072a2a287c24cde38a20ed22db242c7f1f2aeb6202e25f7cbaeacd1f5d39e622c
|
7
|
+
data.tar.gz: 2f16a23515e63fa17773da941ecb099a45c04a9973bcc5224d39ba36b41aff28f49d9e148960101bb49b8f0fca52b34c03c17848795bfb2f9ae4500d42c61d4b
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
@@ -2,11 +2,27 @@
|
|
2
2
|
|
3
3
|
[](https://github.com/fixrb/expresenter/tags)
|
4
4
|
[](https://rubydoc.info/github/fixrb/expresenter/main)
|
5
|
-
[](https://github.com/fixrb/expresenter/actions?query=workflow%3Aruby+branch%3Amain)
|
6
|
-
[](https://github.com/fixrb/expresenter/actions?query=workflow%3Arubocop+branch%3Amain)
|
7
5
|
[](https://github.com/fixrb/expresenter/raw/main/LICENSE.md)
|
8
6
|
|
9
|
-
>
|
7
|
+
> A Ruby gem for presenting test expectation results with rich formatting and requirement level support. Perfect for test frameworks and assertion libraries that need flexible result reporting with support for MUST/SHOULD/MAY requirement levels.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- Rich test result presentation with:
|
12
|
+
- Colored output for different result types (success, warning, info, failure, error)
|
13
|
+
- Single-character indicators for compact output (".", "W", "I", "F", "E")
|
14
|
+
- Emoji support for visual feedback (✅, ⚠️, 💡, ❌, 💥)
|
15
|
+
- ANSI-colored formatted messages with bold titles
|
16
|
+
- Support for RFC 2119 requirement levels (MUST/SHOULD/MAY)
|
17
|
+
- Comprehensive result classification:
|
18
|
+
- Success: Test passed as expected (green)
|
19
|
+
- Warning: Non-critical issues, typically for SHOULD/MAY requirements (yellow)
|
20
|
+
- Info: Additional information about passing tests (blue)
|
21
|
+
- Failure: Test failed but no exception occurred (purple)
|
22
|
+
- Error: Unexpected exceptions during test execution (red)
|
23
|
+
- Built-in support for negative assertions
|
24
|
+
- Detailed error reporting with captured exceptions
|
25
|
+
- Clean integration with test frameworks via simple API
|
10
26
|
|
11
27
|
## Installation
|
12
28
|
|
@@ -30,146 +46,133 @@ gem install expresenter
|
|
30
46
|
|
31
47
|
## Usage
|
32
48
|
|
33
|
-
|
34
|
-
qualifying it with `MUST`, `SHOULD` and `MAY`, we can draw up several scenarios:
|
35
|
-
|
36
|
-
| Requirement levels | **MUST** | **SHOULD** | **MAY** |
|
37
|
-
| ------------------------- | -------- | ---------- | ------- |
|
38
|
-
| Implemented & Matched | `true` | `true` | `true` |
|
39
|
-
| Implemented & Not matched | `false` | `true` | `false` |
|
40
|
-
| Implemented & Exception | `false` | `false` | `false` |
|
41
|
-
| Not implemented | `false` | `false` | `true` |
|
42
|
-
|
43
|
-
Then,
|
44
|
-
|
45
|
-
* for a `true` assertion, a `Expresenter::Pass` instance can be returned;
|
46
|
-
* for a `false` assertion, a `Expresenter::Fail` exception can be raised.
|
47
|
-
|
48
|
-
Both class share a same `Common` interface.
|
49
|
-
|
50
|
-
Passed expectations can be classified as:
|
51
|
-
|
52
|
-
* ✅ success
|
53
|
-
* ⚠️ warning
|
54
|
-
* 💡 info
|
55
|
-
|
56
|
-
Failed expectations can be classified as:
|
57
|
-
|
58
|
-
* ❌ failure
|
59
|
-
* 💥 error
|
60
|
-
|
61
|
-
### Instantiation
|
62
|
-
|
63
|
-
The following parameters are required to instantiate the result:
|
64
|
-
|
65
|
-
* `actual`: Returned value by the challenged subject.
|
66
|
-
* `definition`: A readable string of the matcher and any expected values.
|
67
|
-
* `error`: Any possible raised exception.
|
68
|
-
* `expected`: The expected value.
|
69
|
-
* `got`: The result of the boolean comparison between the actual value and the expected value through the matcher.
|
70
|
-
* `negate`: Evaluated to a negative assertion?
|
71
|
-
* `level`: The requirement level (`:MUST`, `:SHOULD` or `:MAY`).
|
72
|
-
|
73
|
-
#### Examples
|
74
|
-
|
75
|
-
A passed expectation:
|
49
|
+
### Basic Example
|
76
50
|
|
77
51
|
```ruby
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
result.
|
89
|
-
result.
|
90
|
-
result.
|
91
|
-
result.
|
92
|
-
result.
|
93
|
-
result.summary # => "expected \"FOO\" not to eq \"foo\""
|
94
|
-
result.colored_char # => "\e[32m.\e[0m"
|
95
|
-
result.colored_string # => "\e[32m\e[1mSuccess\e[22m: expected \"FOO\" not to eq \"foo\".\e[0m"
|
96
|
-
result.message # => "Success: expected \"FOO\" not to eq \"foo\"."
|
97
|
-
result.to_s # => "Success: expected \"FOO\" not to eq \"foo\"."
|
98
|
-
result.titre # => "Success"
|
52
|
+
# Create a successful test result
|
53
|
+
result = Expresenter.call(true).new(
|
54
|
+
actual: "foo",
|
55
|
+
definition: 'eq "foo"',
|
56
|
+
error: nil,
|
57
|
+
got: true,
|
58
|
+
negate: false,
|
59
|
+
level: :MUST
|
60
|
+
)
|
61
|
+
|
62
|
+
result.passed? # => true
|
63
|
+
result.to_sym # => :success
|
64
|
+
result.char # => "."
|
65
|
+
result.emoji # => "✅"
|
66
|
+
result.to_s # => 'Success: expected "foo" to eq "foo".'
|
99
67
|
```
|
100
68
|
|
101
|
-
|
69
|
+
### Handling Different Result Types
|
102
70
|
|
103
71
|
```ruby
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
72
|
+
# Warning example (non-critical requirement)
|
73
|
+
warning = Expresenter.call(true).new(
|
74
|
+
actual: "foo",
|
75
|
+
definition: "be_optimized",
|
76
|
+
error: nil,
|
77
|
+
got: false, # triggers warning state
|
78
|
+
negate: false,
|
79
|
+
level: :SHOULD
|
80
|
+
)
|
81
|
+
|
82
|
+
warning.warning? # => true
|
83
|
+
warning.char # => "W"
|
84
|
+
warning.emoji # => "⚠️"
|
85
|
+
|
86
|
+
# Failure example with exception
|
87
|
+
begin
|
88
|
+
Expresenter.call(false).with(
|
89
|
+
actual: 42,
|
90
|
+
definition: "eq 43",
|
91
|
+
error: nil,
|
92
|
+
got: false,
|
93
|
+
negate: false,
|
94
|
+
level: :MUST
|
95
|
+
)
|
96
|
+
rescue Expresenter::Fail => e
|
97
|
+
e.failure? # => true
|
98
|
+
e.char # => "F"
|
99
|
+
e.emoji # => "❌"
|
100
|
+
e.to_s # => "Failure: expected 42 to eq 43."
|
101
|
+
end
|
125
102
|
```
|
126
103
|
|
127
|
-
###
|
104
|
+
### Using Requirement Levels
|
128
105
|
|
129
|
-
|
106
|
+
Expresenter supports RFC 2119 requirement levels:
|
130
107
|
|
131
|
-
|
108
|
+
- `:MUST` - Critical requirements that must be satisfied
|
109
|
+
- `:SHOULD` - Recommended requirements that should be satisfied when possible
|
110
|
+
- `:MAY` - Optional requirements that may be satisfied
|
132
111
|
|
133
112
|
```ruby
|
134
|
-
|
113
|
+
# SHOULD requirement with warning
|
114
|
+
result = Expresenter.call(true).new(
|
115
|
+
actual: response,
|
116
|
+
definition: "have_fast_response_time",
|
117
|
+
error: nil,
|
118
|
+
got: false,
|
119
|
+
negate: false,
|
120
|
+
level: :SHOULD
|
121
|
+
)
|
122
|
+
|
123
|
+
result.warning? # => true
|
135
124
|
```
|
136
125
|
|
137
|
-
|
126
|
+
### Working with Negative Assertions
|
138
127
|
|
139
128
|
```ruby
|
140
|
-
|
129
|
+
# Negative assertion example
|
130
|
+
result = Expresenter.call(true).new(
|
131
|
+
actual: "foo",
|
132
|
+
definition: 'eq "bar"',
|
133
|
+
error: nil,
|
134
|
+
got: true,
|
135
|
+
negate: true, # indicates negative assertion
|
136
|
+
level: :MUST
|
137
|
+
)
|
138
|
+
|
139
|
+
result.negate? # => true
|
140
|
+
result.to_s # => 'Success: expected "foo" not to eq "bar".'
|
141
141
|
```
|
142
142
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
> 2: from (irb):43:in `rescue in irb_binding'
|
147
|
-
> 1: from /Users/cyril/github/fixrb/expresenter/lib/expresenter/fail.rb:25:in `with'
|
148
|
-
> Expresenter::Fail (Exception: BOOM.)
|
143
|
+
## Integration
|
144
|
+
|
145
|
+
Expresenter can be easily integrated into test frameworks and assertion libraries. It provides a simple API for creating and handling test results with rich formatting and requirement levels.
|
149
146
|
|
150
|
-
|
147
|
+
Example integration with a test framework:
|
151
148
|
|
152
|
-
|
153
|
-
|
149
|
+
```ruby
|
150
|
+
def assert(actual, matcher, level: :MUST)
|
151
|
+
result = matcher.match(actual)
|
152
|
+
|
153
|
+
Expresenter.call(result.success?).with(
|
154
|
+
actual: actual,
|
155
|
+
definition: matcher.description,
|
156
|
+
error: result.error,
|
157
|
+
got: result.matched?,
|
158
|
+
negate: false,
|
159
|
+
level: level
|
160
|
+
)
|
161
|
+
end
|
162
|
+
```
|
154
163
|
|
155
|
-
##
|
164
|
+
## Development
|
156
165
|
|
157
|
-
|
158
|
-
* Bugs/issues: https://github.com/fixrb/expresenter/issues
|
166
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
159
167
|
|
160
|
-
##
|
168
|
+
## Contributing
|
161
169
|
|
162
|
-
|
170
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/fixrb/expresenter. This project is intended to be a safe, welcoming space for collaboration.
|
163
171
|
|
164
172
|
## License
|
165
173
|
|
166
174
|
The [gem](https://rubygems.org/gems/expresenter) is available as open source under the terms of the [MIT License](https://github.com/fixrb/expresenter/raw/main/LICENSE.md).
|
167
175
|
|
168
|
-
|
176
|
+
## Sponsors
|
169
177
|
|
170
|
-
|
171
|
-
This project is sponsored by:<br />
|
172
|
-
<a href="https://sashite.com/"><img
|
173
|
-
src="https://github.com/fixrb/expresenter/raw/main/img/sashite.png"
|
174
|
-
alt="Sashité" /></a>
|
175
|
-
</p>
|
178
|
+
This project is sponsored by [Sashité](https://sashite.com/)
|
data/lib/expresenter/common.rb
CHANGED
@@ -1,110 +1,170 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Expresenter
|
4
|
-
# Common collection of methods.
|
4
|
+
# Common collection of methods shared between Pass and Fail result classes.
|
5
|
+
#
|
6
|
+
# This module provides the core functionality for presenting test results, including:
|
7
|
+
# - Access to test result data (actual value, definition, error state)
|
8
|
+
# - Test state queries (passed?, error?, success?)
|
9
|
+
# - Result formatting and presentation (summary, colored output)
|
10
|
+
# - Support for requirement levels (MUST/SHOULD/MAY)
|
11
|
+
#
|
12
|
+
# @example Using common methods in a test result
|
13
|
+
# result = Expresenter.call(true).new(
|
14
|
+
# actual: "foo",
|
15
|
+
# definition: 'eq "foo"',
|
16
|
+
# error: nil,
|
17
|
+
# got: true,
|
18
|
+
# negate: false,
|
19
|
+
# level: :MUST
|
20
|
+
# )
|
21
|
+
#
|
22
|
+
# result.passed? # => true
|
23
|
+
# result.success? # => true
|
24
|
+
# result.summary # => 'expected "foo" to eq "foo"'
|
25
|
+
# result.to_s # => 'Success: expected "foo" to eq "foo".'
|
26
|
+
#
|
5
27
|
module Common
|
6
|
-
#
|
28
|
+
# String constant used for joining message parts.
|
29
|
+
#
|
30
|
+
# @api private
|
7
31
|
SPACE = " "
|
8
32
|
|
9
|
-
#
|
33
|
+
# The actual value returned by the test subject.
|
34
|
+
#
|
35
|
+
# @return [#object_id] The value being tested against the expectation.
|
36
|
+
# @example
|
37
|
+
# result.actual # => "foo"
|
10
38
|
attr_reader :actual
|
11
39
|
|
12
|
-
#
|
40
|
+
# The human-readable description of the expectation.
|
41
|
+
#
|
42
|
+
# @return [String] Description combining the matcher and expected values.
|
43
|
+
# @example
|
44
|
+
# result.definition # => 'eq "foo"'
|
13
45
|
attr_reader :definition
|
14
46
|
|
15
|
-
#
|
47
|
+
# Any exception that was raised during the test.
|
48
|
+
#
|
49
|
+
# @return [Exception, nil] The error object if an exception occurred, nil otherwise.
|
50
|
+
# @example
|
51
|
+
# begin
|
52
|
+
# raise StandardError, "Oops"
|
53
|
+
# rescue => e
|
54
|
+
# result = Expresenter.call(false).new(error: e, ...)
|
55
|
+
# result.error # => #<StandardError: Oops>
|
56
|
+
# end
|
16
57
|
attr_reader :error
|
17
58
|
|
18
|
-
#
|
19
|
-
|
20
|
-
|
21
|
-
# @
|
22
|
-
#
|
59
|
+
# The boolean result of comparing the actual value against the expectation.
|
60
|
+
#
|
61
|
+
# @return [#object_id] Usually true/false, but can be any object that responds to equal?
|
62
|
+
# @example
|
63
|
+
# result.got # => true
|
23
64
|
attr_reader :got
|
24
65
|
|
25
|
-
#
|
66
|
+
# The requirement level of the expectation.
|
67
|
+
#
|
68
|
+
# @return [:MUST, :SHOULD, :MAY] The RFC 2119 requirement level.
|
69
|
+
# @see https://www.ietf.org/rfc/rfc2119.txt
|
70
|
+
# @example
|
71
|
+
# result.level # => :MUST
|
26
72
|
attr_reader :level
|
27
73
|
|
28
|
-
#
|
74
|
+
# Checks if the test passed.
|
29
75
|
#
|
30
|
-
# @return [Boolean]
|
76
|
+
# @return [Boolean] true if the test passed, false otherwise.
|
77
|
+
# @example
|
78
|
+
# result.passed? # => true
|
31
79
|
def passed?
|
32
80
|
!failed?
|
33
81
|
end
|
34
82
|
|
35
|
-
#
|
83
|
+
# Checks if this is a negative assertion.
|
36
84
|
#
|
37
|
-
# @return [Boolean]
|
85
|
+
# @return [Boolean] true if this is a negative assertion (using not), false otherwise.
|
86
|
+
# @example
|
87
|
+
# result = Expresenter.call(true).new(negate: true, ...)
|
88
|
+
# result.negate? # => true
|
38
89
|
def negate?
|
39
90
|
@negate
|
40
91
|
end
|
41
92
|
|
42
|
-
#
|
93
|
+
# Checks if an error occurred during the test.
|
43
94
|
#
|
44
|
-
# @return [Boolean]
|
95
|
+
# @return [Boolean] true if an error was captured, false otherwise.
|
96
|
+
# @example
|
97
|
+
# begin
|
98
|
+
# raise "Oops"
|
99
|
+
# rescue => e
|
100
|
+
# result = Expresenter.call(false).new(error: e, ...)
|
101
|
+
# result.error? # => true
|
102
|
+
# end
|
45
103
|
def error?
|
46
104
|
!error.nil?
|
47
105
|
end
|
48
106
|
|
49
|
-
#
|
107
|
+
# Checks if the test was successful.
|
50
108
|
#
|
51
|
-
# @return [Boolean]
|
109
|
+
# @return [Boolean] true if the got value equals true, false otherwise.
|
110
|
+
# @example
|
111
|
+
# result = Expresenter.call(true).new(got: true, ...)
|
112
|
+
# result.success? # => true
|
52
113
|
def success?
|
53
114
|
got.equal?(true)
|
54
115
|
end
|
55
116
|
|
56
|
-
#
|
57
|
-
#
|
58
|
-
# @return [String] The human-readable representation of the result.
|
59
|
-
def inspect
|
60
|
-
"#{self.class}(actual: #{actual.inspect}, " \
|
61
|
-
"definition: #{definition.inspect}, " \
|
62
|
-
"error: #{error.inspect}, " \
|
63
|
-
"expected: #{expected.inspect}, " \
|
64
|
-
"got: #{got.inspect}, " \
|
65
|
-
"negate: #{negate?.inspect}, " \
|
66
|
-
"level: #{level.inspect})"
|
67
|
-
end
|
68
|
-
|
69
|
-
# The summary of the result.
|
117
|
+
# Generates a human-readable summary of the test result.
|
70
118
|
#
|
71
|
-
# @return [String] A
|
119
|
+
# @return [String] A description of what was expected vs what was received.
|
120
|
+
# @example With regular value
|
121
|
+
# result.summary # => 'expected "foo" to eq "bar"'
|
122
|
+
# @example With error
|
123
|
+
# result.summary # => "Unexpected error occurred"
|
72
124
|
def summary
|
73
125
|
if error?
|
74
126
|
error.message
|
75
127
|
elsif actual.is_a?(::Exception)
|
76
128
|
actual.message
|
77
|
-
elsif actual == expected
|
78
|
-
["expected", negation, "to", definition].compact.join(SPACE)
|
79
129
|
else
|
80
130
|
["expected", actual.inspect, negation, "to", definition].compact.join(SPACE)
|
81
131
|
end
|
82
132
|
end
|
83
133
|
|
84
|
-
#
|
134
|
+
# Returns the result indicator with ANSI color codes.
|
85
135
|
#
|
86
|
-
# @return [String]
|
136
|
+
# @return [String] A colored single character indicating the result type.
|
137
|
+
# @example
|
138
|
+
# result.colored_char # => "\e[32m.\e[0m"
|
87
139
|
def colored_char
|
88
140
|
color(char)
|
89
141
|
end
|
90
142
|
|
91
|
-
#
|
143
|
+
# Returns the full result message with ANSI color codes.
|
92
144
|
#
|
93
|
-
# @return [String] A string
|
145
|
+
# @return [String] A colored string with the complete test result.
|
146
|
+
# @example
|
147
|
+
# result.colored_string # => "\e[32mSuccess: expected 1 to eq 1.\e[0m"
|
94
148
|
def colored_string
|
95
149
|
color(to_bold_s)
|
96
150
|
end
|
97
151
|
|
98
|
-
#
|
152
|
+
# Returns the complete result message.
|
99
153
|
#
|
100
|
-
# @return [String] A string
|
154
|
+
# @return [String] A string containing the result type and summary.
|
155
|
+
# @example
|
156
|
+
# result.to_s # => "Success: expected 1 to eq 1."
|
101
157
|
def to_s
|
102
158
|
"#{titre}: #{summary}."
|
103
159
|
end
|
104
160
|
|
105
|
-
#
|
161
|
+
# Returns the title for the result type.
|
106
162
|
#
|
107
|
-
# @return [String]
|
163
|
+
# @return [String] Either the error class name or capitalized result type.
|
164
|
+
# @example Normal result
|
165
|
+
# result.titre # => "Success"
|
166
|
+
# @example Error result
|
167
|
+
# result.titre # => "StandardError"
|
108
168
|
def titre
|
109
169
|
if error?
|
110
170
|
error.class.name
|
@@ -115,16 +175,18 @@ module Expresenter
|
|
115
175
|
|
116
176
|
protected
|
117
177
|
|
118
|
-
#
|
178
|
+
# Returns the negation word if this is a negative assertion.
|
119
179
|
#
|
120
|
-
# @return [String, nil]
|
180
|
+
# @return [String, nil] Returns "not" for negative assertions, nil otherwise.
|
181
|
+
# @api private
|
121
182
|
def negation
|
122
183
|
"not" if negate?
|
123
184
|
end
|
124
185
|
|
125
|
-
#
|
186
|
+
# Returns the result message with a bold title.
|
126
187
|
#
|
127
|
-
# @return [String]
|
188
|
+
# @return [String] The result message with ANSI codes for bold title.
|
189
|
+
# @api private
|
128
190
|
def to_bold_s
|
129
191
|
"\e[1m#{titre}\e[22m: #{summary}."
|
130
192
|
end
|
data/lib/expresenter/fail.rb
CHANGED
@@ -3,44 +3,109 @@
|
|
3
3
|
require_relative "common"
|
4
4
|
|
5
5
|
module Expresenter
|
6
|
-
#
|
6
|
+
# Class responsible for handling and reporting failed test expectations.
|
7
|
+
#
|
8
|
+
# The Fail class represents test failures and errors, inheriting from StandardError
|
9
|
+
# to support exception handling. It distinguishes between two types of failures:
|
10
|
+
# - Regular failures: When an assertion fails but no exception occurred
|
11
|
+
# - Errors: When an unexpected exception was raised during the test
|
12
|
+
#
|
13
|
+
# @example Handling a regular test failure
|
14
|
+
# begin
|
15
|
+
# Expresenter.call(false).with(
|
16
|
+
# actual: 42,
|
17
|
+
# definition: 'eq 43',
|
18
|
+
# error: nil,
|
19
|
+
# got: false,
|
20
|
+
# negate: false,
|
21
|
+
# level: :MUST
|
22
|
+
# )
|
23
|
+
# rescue Expresenter::Fail => e
|
24
|
+
# e.failure? # => true
|
25
|
+
# e.error? # => false
|
26
|
+
# e.to_sym # => :failure
|
27
|
+
# e.char # => "F"
|
28
|
+
# e.emoji # => "❌"
|
29
|
+
# e.to_s # => "Failure: expected 42 to eq 43."
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# @example Handling a test error
|
33
|
+
# begin
|
34
|
+
# error = StandardError.new("Unexpected error")
|
35
|
+
# Expresenter.call(false).with(
|
36
|
+
# actual: nil,
|
37
|
+
# definition: 'be_valid',
|
38
|
+
# error: error,
|
39
|
+
# got: false,
|
40
|
+
# negate: false,
|
41
|
+
# level: :MUST
|
42
|
+
# )
|
43
|
+
# rescue Expresenter::Fail => e
|
44
|
+
# e.failure? # => false
|
45
|
+
# e.error? # => true
|
46
|
+
# e.to_sym # => :error
|
47
|
+
# e.char # => "E"
|
48
|
+
# e.emoji # => "💥"
|
49
|
+
# e.to_s # => "StandardError: Unexpected error."
|
50
|
+
# end
|
7
51
|
class Fail < ::StandardError
|
8
|
-
#
|
52
|
+
# Single character indicator for test failures.
|
53
|
+
# @api private
|
9
54
|
FAILURE_CHAR = "F"
|
10
55
|
|
11
|
-
# Emoji
|
56
|
+
# Emoji indicator for test failures.
|
57
|
+
# @api private
|
12
58
|
FAILURE_EMOJI = "❌"
|
13
59
|
|
14
|
-
#
|
60
|
+
# Single character indicator for test errors.
|
61
|
+
# @api private
|
15
62
|
ERROR_CHAR = "E"
|
16
63
|
|
17
|
-
# Emoji
|
64
|
+
# Emoji indicator for test errors.
|
65
|
+
# @api private
|
18
66
|
ERROR_EMOJI = "💥"
|
19
67
|
|
20
68
|
include Common
|
21
69
|
|
22
|
-
#
|
23
|
-
#
|
70
|
+
# Creates and raises a new Fail instance with the given details.
|
71
|
+
#
|
72
|
+
# @param details [Hash] Test result details (see #initialize for parameters)
|
73
|
+
# @raise [Fail] Always raises a Fail exception with the provided details
|
74
|
+
# @example
|
75
|
+
# Expresenter::Fail.with(
|
76
|
+
# actual: 42,
|
77
|
+
# definition: 'eq 43',
|
78
|
+
# error: nil,
|
79
|
+
# got: false,
|
80
|
+
# negate: false,
|
81
|
+
# level: :MUST
|
82
|
+
# ) # raises Expresenter::Fail
|
24
83
|
def self.with(**details)
|
25
84
|
raise new(**details)
|
26
85
|
end
|
27
86
|
|
28
|
-
#
|
87
|
+
# Initializes a new Fail instance.
|
88
|
+
#
|
89
|
+
# @param actual [#object_id] The actual value returned by the test
|
90
|
+
# @param definition [String] Human-readable description of the expectation
|
91
|
+
# @param error [Exception, nil] Any exception that was raised during the test
|
92
|
+
# @param got [Boolean, nil] Result of comparing actual vs expected values
|
93
|
+
# @param negate [Boolean] Whether this is a negative assertion
|
94
|
+
# @param level [:MUST, :SHOULD, :MAY] The requirement level of the test
|
29
95
|
#
|
30
|
-
# @
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
def initialize(actual:, definition:, error:,
|
96
|
+
# @example Creating a failure result
|
97
|
+
# Fail.new(
|
98
|
+
# actual: 42,
|
99
|
+
# definition: 'eq 43',
|
100
|
+
# error: nil,
|
101
|
+
# got: false,
|
102
|
+
# negate: false,
|
103
|
+
# level: :MUST
|
104
|
+
# )
|
105
|
+
def initialize(actual:, definition:, error:, got:, negate:, level:)
|
40
106
|
@actual = actual
|
41
107
|
@definition = definition
|
42
108
|
@error = error
|
43
|
-
@expected = expected
|
44
109
|
@got = got
|
45
110
|
@negate = negate
|
46
111
|
@level = level
|
@@ -48,37 +113,37 @@ module Expresenter
|
|
48
113
|
super(to_s)
|
49
114
|
end
|
50
115
|
|
51
|
-
#
|
116
|
+
# Always returns true since this class represents failed tests.
|
52
117
|
#
|
53
|
-
# @return [
|
118
|
+
# @return [true] Always returns true
|
54
119
|
def failed?
|
55
120
|
true
|
56
121
|
end
|
57
122
|
|
58
|
-
#
|
123
|
+
# Indicates if this is a regular failure (not an error).
|
59
124
|
#
|
60
|
-
# @return [Boolean]
|
125
|
+
# @return [Boolean] true if no error was captured, false otherwise
|
61
126
|
def failure?
|
62
127
|
!error?
|
63
128
|
end
|
64
129
|
|
65
|
-
#
|
130
|
+
# Fail results are never informational.
|
66
131
|
#
|
67
|
-
# @return [
|
132
|
+
# @return [false] Always returns false
|
68
133
|
def info?
|
69
134
|
false
|
70
135
|
end
|
71
136
|
|
72
|
-
#
|
137
|
+
# Fail results are never warnings.
|
73
138
|
#
|
74
|
-
# @return [
|
139
|
+
# @return [false] Always returns false
|
75
140
|
def warning?
|
76
141
|
false
|
77
142
|
end
|
78
143
|
|
79
|
-
#
|
144
|
+
# Returns the symbolic representation of the failure type.
|
80
145
|
#
|
81
|
-
# @return [
|
146
|
+
# @return [:failure, :error] :failure for regular failures, :error when an exception occurred
|
82
147
|
def to_sym
|
83
148
|
if failure?
|
84
149
|
:failure
|
@@ -87,9 +152,9 @@ module Expresenter
|
|
87
152
|
end
|
88
153
|
end
|
89
154
|
|
90
|
-
#
|
155
|
+
# Returns a single character representing the failure type.
|
91
156
|
#
|
92
|
-
# @return [String]
|
157
|
+
# @return [String] "F" for failures, "E" for errors
|
93
158
|
def char
|
94
159
|
if failure?
|
95
160
|
FAILURE_CHAR
|
@@ -98,9 +163,9 @@ module Expresenter
|
|
98
163
|
end
|
99
164
|
end
|
100
165
|
|
101
|
-
#
|
166
|
+
# Returns an emoji representing the failure type.
|
102
167
|
#
|
103
|
-
# @return [String]
|
168
|
+
# @return [String] "❌" for failures, "💥" for errors
|
104
169
|
def emoji
|
105
170
|
if failure?
|
106
171
|
FAILURE_EMOJI
|
@@ -111,6 +176,11 @@ module Expresenter
|
|
111
176
|
|
112
177
|
protected
|
113
178
|
|
179
|
+
# Applies color formatting to the given string based on failure type.
|
180
|
+
#
|
181
|
+
# @param str [String] The string to colorize
|
182
|
+
# @return [String] ANSI-colored string (purple for failures, red for errors)
|
183
|
+
# @api private
|
114
184
|
def color(str)
|
115
185
|
if failure?
|
116
186
|
"\e[35m#{str}\e[0m" # purple
|
data/lib/expresenter/pass.rb
CHANGED
@@ -3,88 +3,143 @@
|
|
3
3
|
require_relative "common"
|
4
4
|
|
5
5
|
module Expresenter
|
6
|
-
#
|
6
|
+
# Class responsible for handling and reporting successful test expectations.
|
7
|
+
#
|
8
|
+
# The Pass class represents test results that didn't fail, but can be in different states:
|
9
|
+
# - Success: Test passed completely as expected
|
10
|
+
# - Warning: Test passed but with some concerns (typically for :SHOULD or :MAY requirements)
|
11
|
+
# - Info: Test passed but with additional information to note
|
12
|
+
#
|
13
|
+
# Each state has its own character indicator, emoji, and color for easy visual identification
|
14
|
+
# in test output:
|
15
|
+
# - Success: "." (green) ✅
|
16
|
+
# - Warning: "W" (yellow) ⚠️
|
17
|
+
# - Info: "I" (blue) 💡
|
18
|
+
#
|
19
|
+
# @example Creating a successful test result
|
20
|
+
# result = Expresenter::Pass.new(
|
21
|
+
# actual: "foo",
|
22
|
+
# definition: 'eq "foo"',
|
23
|
+
# error: nil,
|
24
|
+
# got: true,
|
25
|
+
# negate: false,
|
26
|
+
# level: :MUST
|
27
|
+
# )
|
28
|
+
# result.success? # => true
|
29
|
+
# result.warning? # => false
|
30
|
+
# result.info? # => false
|
31
|
+
# result.to_sym # => :success
|
32
|
+
# result.char # => "."
|
33
|
+
# result.emoji # => "✅"
|
34
|
+
# result.to_s # => "Success: expected \"foo\" to eq \"foo\"."
|
35
|
+
#
|
36
|
+
# @example Creating a warning result
|
37
|
+
# result = Expresenter::Pass.new(
|
38
|
+
# actual: "foo",
|
39
|
+
# definition: 'eq "foo"',
|
40
|
+
# error: nil,
|
41
|
+
# got: false, # Warning state is triggered when got: false
|
42
|
+
# negate: false,
|
43
|
+
# level: :SHOULD
|
44
|
+
# )
|
45
|
+
# result.warning? # => true
|
46
|
+
# result.to_sym # => :warning
|
47
|
+
# result.char # => "W"
|
48
|
+
# result.emoji # => "⚠️"
|
7
49
|
class Pass
|
8
|
-
#
|
50
|
+
# Single character indicator for informational results.
|
51
|
+
# @api private
|
9
52
|
INFO_CHAR = "I"
|
10
53
|
|
11
|
-
# Emoji
|
54
|
+
# Emoji indicator for informational results.
|
55
|
+
# @api private
|
12
56
|
INFO_EMOJI = "💡"
|
13
57
|
|
14
|
-
#
|
58
|
+
# Single character indicator for successful results.
|
59
|
+
# @api private
|
15
60
|
SUCCESS_CHAR = "."
|
16
61
|
|
17
|
-
# Emoji
|
62
|
+
# Emoji indicator for successful results.
|
63
|
+
# @api private
|
18
64
|
SUCCESS_EMOJI = "✅"
|
19
65
|
|
20
|
-
#
|
66
|
+
# Single character indicator for warning results.
|
67
|
+
# @api private
|
21
68
|
WARNING_CHAR = "W"
|
22
69
|
|
23
|
-
# Emoji
|
70
|
+
# Emoji indicator for warning results.
|
71
|
+
# @api private
|
24
72
|
WARNING_EMOJI = "⚠️"
|
25
73
|
|
26
74
|
include Common
|
27
75
|
|
28
|
-
#
|
29
|
-
#
|
76
|
+
# Creates a new Pass instance with the given details.
|
77
|
+
#
|
78
|
+
# @param details [Hash] Test result details (see #initialize for parameters)
|
79
|
+
# @return [Pass] A new Pass instance
|
80
|
+
# @example
|
81
|
+
# Expresenter::Pass.with(
|
82
|
+
# actual: "foo",
|
83
|
+
# definition: 'eq "foo"',
|
84
|
+
# error: nil,
|
85
|
+
# got: true,
|
86
|
+
# negate: false,
|
87
|
+
# level: :MUST
|
88
|
+
# )
|
30
89
|
def self.with(**details)
|
31
90
|
new(**details)
|
32
91
|
end
|
33
92
|
|
34
93
|
alias message to_s
|
35
94
|
|
36
|
-
#
|
95
|
+
# Initializes a new Pass instance.
|
37
96
|
#
|
38
|
-
# @param actual
|
39
|
-
# @param definition [String]
|
40
|
-
#
|
41
|
-
# @param
|
42
|
-
# @param
|
43
|
-
# @param
|
44
|
-
|
45
|
-
# @param negate [Boolean] Evaluated to a negative assertion?
|
46
|
-
# @param level [:MUST, :SHOULD, :MAY] The requirement level.
|
47
|
-
def initialize(actual:, definition:, error:, expected:, got:, negate:, level:)
|
97
|
+
# @param actual [#object_id] The actual value returned by the test
|
98
|
+
# @param definition [String] Human-readable description of the expectation
|
99
|
+
# @param error [Exception, nil] Any exception that occurred (for info states)
|
100
|
+
# @param got [Boolean, nil] Result of comparing actual vs expected values
|
101
|
+
# @param negate [Boolean] Whether this is a negative assertion
|
102
|
+
# @param level [:MUST, :SHOULD, :MAY] The requirement level of the test
|
103
|
+
def initialize(actual:, definition:, error:, got:, negate:, level:)
|
48
104
|
@actual = actual
|
49
105
|
@definition = definition
|
50
106
|
@error = error
|
51
|
-
@expected = expected
|
52
107
|
@got = got
|
53
108
|
@negate = negate
|
54
109
|
@level = level
|
55
110
|
end
|
56
111
|
|
57
|
-
#
|
112
|
+
# Always returns false since this class represents passed tests.
|
58
113
|
#
|
59
|
-
# @return [
|
114
|
+
# @return [false] Always returns false
|
60
115
|
def failed?
|
61
116
|
false
|
62
117
|
end
|
63
118
|
|
64
|
-
#
|
119
|
+
# Pass results are never failures.
|
65
120
|
#
|
66
|
-
# @return [
|
121
|
+
# @return [false] Always returns false
|
67
122
|
def failure?
|
68
123
|
false
|
69
124
|
end
|
70
125
|
|
71
|
-
#
|
126
|
+
# Indicates if this is an informational result.
|
72
127
|
#
|
73
|
-
# @return [Boolean]
|
128
|
+
# @return [Boolean] true if an error was captured but the test still passed
|
74
129
|
def info?
|
75
130
|
!error.nil?
|
76
131
|
end
|
77
132
|
|
78
|
-
#
|
133
|
+
# Indicates if this is a warning result.
|
79
134
|
#
|
80
|
-
# @return [Boolean]
|
135
|
+
# @return [Boolean] true if got equals false, indicating a non-critical issue
|
81
136
|
def warning?
|
82
137
|
got.equal?(false)
|
83
138
|
end
|
84
139
|
|
85
|
-
#
|
140
|
+
# Returns the symbolic representation of the result state.
|
86
141
|
#
|
87
|
-
# @return [
|
142
|
+
# @return [:success, :warning, :info] The type of pass result
|
88
143
|
def to_sym
|
89
144
|
if success?
|
90
145
|
:success
|
@@ -95,9 +150,9 @@ module Expresenter
|
|
95
150
|
end
|
96
151
|
end
|
97
152
|
|
98
|
-
#
|
153
|
+
# Returns a single character representing the result state.
|
99
154
|
#
|
100
|
-
# @return [String]
|
155
|
+
# @return [String] "." for success, "W" for warning, "I" for info
|
101
156
|
def char
|
102
157
|
if success?
|
103
158
|
SUCCESS_CHAR
|
@@ -108,9 +163,9 @@ module Expresenter
|
|
108
163
|
end
|
109
164
|
end
|
110
165
|
|
111
|
-
#
|
166
|
+
# Returns an emoji representing the result state.
|
112
167
|
#
|
113
|
-
# @return [String]
|
168
|
+
# @return [String] "✅" for success, "⚠️" for warning, "💡" for info
|
114
169
|
def emoji
|
115
170
|
if success?
|
116
171
|
SUCCESS_EMOJI
|
@@ -123,6 +178,11 @@ module Expresenter
|
|
123
178
|
|
124
179
|
protected
|
125
180
|
|
181
|
+
# Applies color formatting to the given string based on result state.
|
182
|
+
#
|
183
|
+
# @param str [String] The string to colorize
|
184
|
+
# @return [String] ANSI-colored string (green for success, yellow for warning, blue for info)
|
185
|
+
# @api private
|
126
186
|
def color(str)
|
127
187
|
if success?
|
128
188
|
"\e[32m#{str}\e[0m" # green
|
data/lib/expresenter.rb
CHANGED
@@ -2,15 +2,58 @@
|
|
2
2
|
|
3
3
|
# Namespace for the Expresenter library.
|
4
4
|
#
|
5
|
-
#
|
6
|
-
#
|
5
|
+
# The Expresenter library provides a flexible way to present test expectation results with rich
|
6
|
+
# formatting and requirement level support. It is designed to work with test frameworks and
|
7
|
+
# assertion libraries that need detailed result reporting.
|
8
|
+
#
|
9
|
+
# Each expectation result can be categorized as:
|
10
|
+
# - Success: The test passed as expected
|
11
|
+
# - Warning: A non-critical test failure (typically for :SHOULD or :MAY requirements)
|
12
|
+
# - Info: Additional information about the test
|
13
|
+
# - Failure: A critical test failure
|
14
|
+
# - Error: An unexpected error occurred during the test
|
15
|
+
#
|
16
|
+
# @example Creating a passing expectation result
|
17
|
+
# result = Expresenter.call(true).with(
|
18
|
+
# actual: "FOO",
|
19
|
+
# definition: 'eql "foo"',
|
20
|
+
# error: nil,
|
21
|
+
# got: true,
|
22
|
+
# negate: true,
|
23
|
+
# level: :MUST
|
24
|
+
# )
|
25
|
+
# result.passed? # => true
|
26
|
+
# result.to_s # => "Success: expected \"FOO\" not to eql \"foo\"."
|
27
|
+
#
|
28
|
+
# @example Creating a failing expectation result
|
29
|
+
# # This will raise an Expresenter::Fail exception
|
30
|
+
# Expresenter.call(false).with(
|
31
|
+
# actual: "foo",
|
32
|
+
# definition: "eq 42",
|
33
|
+
# error: Exception.new("Test failed"),
|
34
|
+
# got: false,
|
35
|
+
# negate: false,
|
36
|
+
# level: :MUST
|
37
|
+
# )
|
38
|
+
#
|
7
39
|
module Expresenter
|
8
|
-
#
|
40
|
+
# Factory method that returns the appropriate result class based on the assertion outcome.
|
41
|
+
#
|
42
|
+
# @param is_passed [Boolean] The value of the assertion. True indicates a passing test,
|
43
|
+
# false indicates a failing test.
|
44
|
+
#
|
45
|
+
# @return [Class<Pass>, Class<Fail>] Returns the Pass class for passing tests or
|
46
|
+
# the Fail class for failing tests. These classes can then be instantiated with
|
47
|
+
# detailed test information using #with.
|
48
|
+
#
|
49
|
+
# @example Getting a Pass class for a successful test
|
50
|
+
# result_class = Expresenter.call(true)
|
51
|
+
# result_class # => Expresenter::Pass
|
9
52
|
#
|
10
|
-
# @
|
53
|
+
# @example Getting a Fail class for a failed test
|
54
|
+
# result_class = Expresenter.call(false)
|
55
|
+
# result_class # => Expresenter::Fail
|
11
56
|
#
|
12
|
-
# @example Get the pass class result.
|
13
|
-
# call(true) # => Pass
|
14
57
|
def self.call(is_passed)
|
15
58
|
is_passed ? Pass : Fail
|
16
59
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: expresenter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Kato
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-31 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Expectation result presenter.
|
14
14
|
email: contact@cyril.email
|
@@ -27,7 +27,7 @@ licenses:
|
|
27
27
|
- MIT
|
28
28
|
metadata:
|
29
29
|
rubygems_mfa_required: 'true'
|
30
|
-
post_install_message:
|
30
|
+
post_install_message:
|
31
31
|
rdoc_options: []
|
32
32
|
require_paths:
|
33
33
|
- lib
|
@@ -35,15 +35,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
35
35
|
requirements:
|
36
36
|
- - ">="
|
37
37
|
- !ruby/object:Gem::Version
|
38
|
-
version: 3.
|
38
|
+
version: 3.1.0
|
39
39
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
40
|
requirements:
|
41
41
|
- - ">="
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
version: '0'
|
44
44
|
requirements: []
|
45
|
-
rubygems_version: 3.
|
46
|
-
signing_key:
|
45
|
+
rubygems_version: 3.3.27
|
46
|
+
signing_key:
|
47
47
|
specification_version: 4
|
48
48
|
summary: Expectation result presenter.
|
49
49
|
test_files: []
|