expresenter 1.4.1 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Version](https://img.shields.io/github/v/tag/fixrb/expresenter?label=Version&logo=github)](https://github.com/fixrb/expresenter/tags)
|
4
4
|
[![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/fixrb/expresenter/main)
|
5
|
-
[![Ruby](https://github.com/fixrb/expresenter/workflows/Ruby/badge.svg?branch=main)](https://github.com/fixrb/expresenter/actions?query=workflow%3Aruby+branch%3Amain)
|
6
|
-
[![RuboCop](https://github.com/fixrb/expresenter/workflows/RuboCop/badge.svg?branch=main)](https://github.com/fixrb/expresenter/actions?query=workflow%3Arubocop+branch%3Amain)
|
7
5
|
[![License](https://img.shields.io/github/license/fixrb/expresenter?label=License&logo=github)](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: []
|