rspec_approvals 0.8.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +247 -0
- data/lib/rspec_approvals.rb +12 -0
- data/lib/rspec_approvals/approval_handler.rb +99 -0
- data/lib/rspec_approvals/extensions/file.rb +9 -0
- data/lib/rspec_approvals/matchers/base.rb +148 -0
- data/lib/rspec_approvals/matchers/match_approval.rb +15 -0
- data/lib/rspec_approvals/matchers/output_approval.rb +43 -0
- data/lib/rspec_approvals/matchers/raise_approval.rb +30 -0
- data/lib/rspec_approvals/module_functions.rb +11 -0
- data/lib/rspec_approvals/rspec_config.rb +14 -0
- data/lib/rspec_approvals/stream.rb +41 -0
- data/lib/rspec_approvals/version.rb +3 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bf7766ed4c1e6890d248ccabf8e6cd02c6656f07f702e95b95c06a89ef1b7fd2
|
4
|
+
data.tar.gz: ec5c8219a4129d2bcfc8a3ebfa4bdcf5cdf658a0a08a677906e0bffa8cd8e4c1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7d3fb8aa6519c178621967564338eb822973660034511bf753156fbffa88468aaad58bc0c5e7367d56c2a7759d788b2b40f539576483ea16c9a5cada7f7f2c90
|
7
|
+
data.tar.gz: 5e503b9463bc9ed88de91e98a1e72e91be4a987241acda2a6673a63f6dd2b32a5e2639016864637dc4faa7902789132c9e28b64c3cf3781f6f3a0e0fb99bf9c3
|
data/README.md
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
# RSpec Approvals
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/rspec_approvals.svg)](https://badge.fury.io/rb/rspec_approvals)
|
4
|
+
[![Build Status](https://github.com/DannyBen/rspec_approvals/workflows/Test/badge.svg)](https://github.com/DannyBen/rspec_approvals/actions?query=workflow%3ATest)
|
5
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/a06ed5e30412062c454c/maintainability)](https://codeclimate.com/github/DannyBen/rspec_approvals/maintainability)
|
6
|
+
|
7
|
+
---
|
8
|
+
|
9
|
+
RSpec Approvals allows you to interactively review and approve testable
|
10
|
+
content.
|
11
|
+
|
12
|
+
![Demo](demo/cast.svg)
|
13
|
+
|
14
|
+
---
|
15
|
+
|
16
|
+
## Install
|
17
|
+
|
18
|
+
```
|
19
|
+
$ gem install rspec_approvals
|
20
|
+
```
|
21
|
+
|
22
|
+
Or with bundler:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem 'rspec_approvals'
|
26
|
+
```
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
Require the gem in your spec helper:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
# spec/spec_helper.rb
|
34
|
+
require 'rspec_approvals'
|
35
|
+
```
|
36
|
+
|
37
|
+
And use any of the matchers in your specs.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
describe 'ls' do
|
41
|
+
it "works" do
|
42
|
+
expect(`ls`).to match_approval('ls_approval')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
## Matchers
|
48
|
+
|
49
|
+
### `match_approval` - Compare Strings
|
50
|
+
|
51
|
+
Compare a string with a pre-approved approval.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
expect('some string').to match_approval('approval_filename')
|
55
|
+
```
|
56
|
+
|
57
|
+
|
58
|
+
### `output_approval` - Compare STDOUT/STDERR
|
59
|
+
|
60
|
+
Compare an output (stdout or stderr) with a pre-approved approval.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
expect { puts "hello" }.to output_approval('approval_filename')
|
64
|
+
expect { puts "hello" }.to output_approval('approval_filename').to_stdout
|
65
|
+
expect { $stderr.puts "hello" }.to output_approval('approval_filename').to_stderr
|
66
|
+
|
67
|
+
# The first two are the same, as the default stream is stdout.
|
68
|
+
```
|
69
|
+
|
70
|
+
|
71
|
+
### `raise_approval` - Compare raised exceptions
|
72
|
+
|
73
|
+
Compare a raised exception with a pre-approved approval.
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
expect { raise 'some error' }.to raise_approval('approval_filename')
|
77
|
+
```
|
78
|
+
|
79
|
+
## Modifiers
|
80
|
+
|
81
|
+
### `diff` - String similarity
|
82
|
+
|
83
|
+
Adding `diff(distance)` to either `match_approval` or `output_approval` will
|
84
|
+
change the matching behavior. Instead of expecting the strings to be exactly
|
85
|
+
the same, using `diff` compares the strings using the
|
86
|
+
[Levenshtein distance][levenshtein] algorithm.
|
87
|
+
|
88
|
+
In the below example, we allow up to 5 characters to be different.
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
expect ('some string').to match_approval('approval_filename').diff(5)
|
92
|
+
expect { puts 'some string' }.to output_approval('approval_filename').diff(5)
|
93
|
+
```
|
94
|
+
|
95
|
+
### `except` - Exclude by regular expression
|
96
|
+
|
97
|
+
Adding `except(regex)` to either `match_approval` or `output_approval` will
|
98
|
+
modify the string under test before running. By default, the regular
|
99
|
+
expression will be replaced with `...`.
|
100
|
+
|
101
|
+
In the below example, we ignore the full path of the file.
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
expect('path: /path/to/file').to match_approval('approval_filename').except(/path: .*file/)
|
105
|
+
```
|
106
|
+
|
107
|
+
You may provide a second argument, which will be used as an alternative
|
108
|
+
replace string:
|
109
|
+
|
110
|
+
In the below example, all time strings will be replaced with `HH:MM`:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
expect('22:30').to match_approval('approval_filename').except(/\d2:\d2/, 'HH:MM')
|
114
|
+
```
|
115
|
+
|
116
|
+
### `before` - Alter the string before testing
|
117
|
+
|
118
|
+
The `before(proc)` method is a low level method and should normally not be
|
119
|
+
used directly (as it is used by the `except` modifier).
|
120
|
+
|
121
|
+
Adding `before(proc)` to either `match_approval` or `output_approval` will
|
122
|
+
call the block and supply the actual string. The proc is expected to return
|
123
|
+
the new actual string.
|
124
|
+
|
125
|
+
In the below example, we replace all email addresses in a string.
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
expect('hello rspec@approvals.com').to match_approval('approval_filename').before ->(actual) do
|
129
|
+
actual.gsub /\w+@\w+\.\w+/, 'some@email.com'
|
130
|
+
end
|
131
|
+
|
132
|
+
```
|
133
|
+
|
134
|
+
## Configuration
|
135
|
+
|
136
|
+
### `interactive_approvals`
|
137
|
+
|
138
|
+
By default, interactive approvals are enabled in any environment that
|
139
|
+
does not define the `CI` or the `GITHUB_ACTIONS` environment variables.
|
140
|
+
You can change this by adding this to your `spec_helper`
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
RSpec.configure do |config|
|
144
|
+
config.interactive_approvals = false # or any logic
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
### `approvals_path`
|
149
|
+
|
150
|
+
By default, approvals are stored in `spec/approvals`. To change the path,
|
151
|
+
add this to your `spec_helper`.
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
RSpec.configure do |config|
|
155
|
+
config.approvals_path = 'spec/anywhere/else'
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
159
|
+
### `auto_approve`
|
160
|
+
|
161
|
+
If you wish to automatically approve all new or changed approvals, you can
|
162
|
+
set the `auto_approve` configuration option to `true`. By default,
|
163
|
+
auto approval is enabled if the environment variable `AUTO_APPROVE` is set.
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
RSpec.configure do |config|
|
167
|
+
config.auto_approve = true # or any logic
|
168
|
+
end
|
169
|
+
```
|
170
|
+
|
171
|
+
This feature is intended to help clean up the approvals folder from old, no
|
172
|
+
longer used files. Simply run the specs once, to ensure they all oass,
|
173
|
+
delete the approvals folder, and run the specs again with:
|
174
|
+
|
175
|
+
```
|
176
|
+
$ AUTO_APPROVE=1 rspec
|
177
|
+
```
|
178
|
+
|
179
|
+
### `strip_ansi_escape`
|
180
|
+
|
181
|
+
In case your output strings contain ANSI escape codes that you wish to avoid
|
182
|
+
storing in your approvals, you can set the `strip_ansi_escape` to `true`.
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
RSpec.configure do |config|
|
186
|
+
config.strip_ansi_escape = true
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
190
|
+
### `before_approval`
|
191
|
+
|
192
|
+
In case you need to alter the actual output globally, you can provide the
|
193
|
+
`before_approval` option with a proc. The proc will receive the actual
|
194
|
+
output - similarly to the `before` modifier - and is expectedd to return
|
195
|
+
a modified actual string.
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
RSpec.configure do |config|
|
199
|
+
config.before_approval = ->(actual) do
|
200
|
+
# return the actual string, without IP addresses
|
201
|
+
actual.gsub(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/, '[IP REMOVED]')
|
202
|
+
end
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
206
|
+
## Advanced Usage Tips
|
207
|
+
|
208
|
+
### Sending output directly to RSpecApprovals
|
209
|
+
|
210
|
+
In some cases, you might need to send output directly to the `RSpecApproval`
|
211
|
+
stream capturer.
|
212
|
+
|
213
|
+
An example use case, is when you are testing `Logger` output.
|
214
|
+
|
215
|
+
The `RSpecApproval#stdout` and `RSpecApproval#stderr` can be used as an
|
216
|
+
alternative to `$stdout` and `$stderr`. These methods both return the
|
217
|
+
`StringIO` object that is used by `RSpecApprovals` to capture the output.
|
218
|
+
|
219
|
+
For example, you can use this:
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
logger = Logger.new(RSpecApprovals.stdout)
|
223
|
+
```
|
224
|
+
|
225
|
+
as an alternative to this:
|
226
|
+
|
227
|
+
```
|
228
|
+
logger = Logger.new($stdout)
|
229
|
+
```
|
230
|
+
|
231
|
+
### Consistent terminal width
|
232
|
+
|
233
|
+
In case you are testing standard output with long lines, you may encounter inconsistencies when testing on different hosts, with varying terminal width. In order to ensure consistent output to stdout, you may want to set a known terminal size in your `spec_helper`:
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
ENV['COLUMNS'] = '80'
|
237
|
+
ENV['LINES'] = '24'
|
238
|
+
```
|
239
|
+
|
240
|
+
## Contributing / Support
|
241
|
+
|
242
|
+
If you experience any issue, have a question or a suggestion, or if you wish
|
243
|
+
to contribute, feel free to [open an issue][issues].
|
244
|
+
|
245
|
+
|
246
|
+
[levenshtein]: https://en.wikipedia.org/wiki/Levenshtein_distance
|
247
|
+
[issues]: https://github.com/DannyBen/rspec_approvals/issues
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rspec_approvals/extensions/file'
|
2
|
+
|
3
|
+
require 'rspec_approvals/module_functions'
|
4
|
+
require 'rspec_approvals/stream'
|
5
|
+
require 'rspec_approvals/approval_handler'
|
6
|
+
require 'rspec_approvals/matchers/base'
|
7
|
+
require 'rspec_approvals/matchers/match_approval'
|
8
|
+
require 'rspec_approvals/matchers/output_approval'
|
9
|
+
require 'rspec_approvals/matchers/raise_approval'
|
10
|
+
|
11
|
+
require 'rspec_approvals/rspec_config'
|
12
|
+
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'io/console'
|
2
|
+
require 'colsole'
|
3
|
+
require 'tty-prompt'
|
4
|
+
require 'diffy'
|
5
|
+
|
6
|
+
module RSpecApprovals
|
7
|
+
|
8
|
+
# Handles user input and interactive approvals
|
9
|
+
class ApprovalHandler
|
10
|
+
include Colsole
|
11
|
+
|
12
|
+
attr_reader :expected, :actual, :approval_file
|
13
|
+
|
14
|
+
def run(expected, actual, approval_file)
|
15
|
+
@expected = expected
|
16
|
+
@actual = actual
|
17
|
+
@approval_file = approval_file
|
18
|
+
|
19
|
+
show expected.empty? ? actual : diff
|
20
|
+
prompt_user
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def prompt_user
|
26
|
+
response = auto_approve? ? :approve : get_response
|
27
|
+
|
28
|
+
case response
|
29
|
+
|
30
|
+
when :approve, :reject
|
31
|
+
send response
|
32
|
+
|
33
|
+
when :actual, :expected, :diff
|
34
|
+
show send response
|
35
|
+
prompt_user
|
36
|
+
|
37
|
+
else
|
38
|
+
false
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def auto_approve?
|
44
|
+
RSpec.configuration.auto_approve
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_response
|
48
|
+
prompt.select "Please Choose:", menu_options, symbols: { marker: '>' }
|
49
|
+
rescue TTY::Reader::InputInterrupt
|
50
|
+
# :nocov:
|
51
|
+
return :reject
|
52
|
+
# :nocov:
|
53
|
+
end
|
54
|
+
|
55
|
+
def menu_options
|
56
|
+
base = {
|
57
|
+
'Reject (and fail test)' => :reject,
|
58
|
+
'Approve (and save)' => :approve,
|
59
|
+
}
|
60
|
+
|
61
|
+
extra = {
|
62
|
+
'Show actual output' => :actual,
|
63
|
+
'Show expected output' => :expected,
|
64
|
+
'Show diff' => :diff,
|
65
|
+
}
|
66
|
+
|
67
|
+
expected.empty? ? base : base.merge(extra)
|
68
|
+
end
|
69
|
+
|
70
|
+
def approve
|
71
|
+
say "!txtgrn!Approved"
|
72
|
+
File.deep_write approval_file, actual
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
def reject
|
77
|
+
say "!txtred!Not Approved"
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
81
|
+
def separator
|
82
|
+
"!txtgrn!" + ('_' * terminal_width)
|
83
|
+
end
|
84
|
+
|
85
|
+
def diff
|
86
|
+
Diffy::Diff.new(expected, actual, context: 2).to_s :color
|
87
|
+
end
|
88
|
+
|
89
|
+
def show(what)
|
90
|
+
say separator
|
91
|
+
say what
|
92
|
+
say separator
|
93
|
+
end
|
94
|
+
|
95
|
+
def prompt
|
96
|
+
@prompt ||= TTY::Prompt.new
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module RSpecApprovals
|
2
|
+
module Matchers
|
3
|
+
|
4
|
+
# A base matcher for approvals
|
5
|
+
class Base
|
6
|
+
attr_reader :approval_name, :actual, :distance, :actual_distance
|
7
|
+
|
8
|
+
def initialize(approval_name=nil)
|
9
|
+
@before = nil
|
10
|
+
@approval_name = approval_name
|
11
|
+
end
|
12
|
+
|
13
|
+
# Called by RSpec. This will be overridden by child matchers.
|
14
|
+
def matches?(actual)
|
15
|
+
@actual ||= actual
|
16
|
+
return false if @actual.empty?
|
17
|
+
|
18
|
+
@actual = sanitize @actual
|
19
|
+
success = strings_match?
|
20
|
+
|
21
|
+
if success or !interactive?
|
22
|
+
success
|
23
|
+
else
|
24
|
+
approve_approval
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Enables the ability to do something like:
|
29
|
+
# `expect(string).to match_approval(file).diff(10)
|
30
|
+
# The distance argument is the max allowed Levenshtein Distance.
|
31
|
+
def diff(distance)
|
32
|
+
@distance = distance
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# Enables the ability to do something like:
|
37
|
+
# `expect(string).to match_approval(file).except(/\d+/)
|
38
|
+
def except(regex, replace = '...')
|
39
|
+
before ->(str) do
|
40
|
+
str.gsub regex, replace
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Enables the ability to adjust the actual string before checking
|
45
|
+
# for matches:
|
46
|
+
# `expect(a).to match_approval(f).before ->(actual) { actual.gsub /one/, 'two' }`
|
47
|
+
def before(proc)
|
48
|
+
@before ||= []
|
49
|
+
@before << proc
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the expected value, from an approval file
|
54
|
+
def expected
|
55
|
+
@expected ||= expected!
|
56
|
+
end
|
57
|
+
|
58
|
+
# Called by RSpec when there is a failure
|
59
|
+
def failure_message
|
60
|
+
return "actual string is empty" if actual.empty?
|
61
|
+
|
62
|
+
result = "expected: #{actual}\nto match: #{expected}"
|
63
|
+
|
64
|
+
if distance
|
65
|
+
result = "#{result}\n(actual distance is #{actual_distance} instead of the expected #{distance})"
|
66
|
+
end
|
67
|
+
|
68
|
+
result
|
69
|
+
end
|
70
|
+
|
71
|
+
# Lets RSpec know these matchers support diffing
|
72
|
+
def diffable?
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns true if RSpec is configured to sanitize (remove ANSI escape
|
77
|
+
# codes) from the actual strings before proceeeding to comparing them.
|
78
|
+
def sanitize?
|
79
|
+
RSpec.configuration.strip_ansi_escape
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns true if RSpec is configured to allow interactivity.
|
83
|
+
# By default, interactivity is enabled unless the environment
|
84
|
+
# variable `CI` is set.
|
85
|
+
def interactive?
|
86
|
+
RSpec.configuration.interactive_approvals
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the path to the approvals directory.
|
90
|
+
# Default: `spec/approvals`
|
91
|
+
def approvals_dir
|
92
|
+
RSpec.configuration.approvals_path
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the path to the approval file
|
96
|
+
def approval_file
|
97
|
+
"#{approvals_dir}/#{approval_name}"
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
# Asks for user approval. Used by child classes.
|
103
|
+
def approve_approval
|
104
|
+
approval_handler = ApprovalHandler.new
|
105
|
+
approval_handler.run expected, actual, approval_file
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns the actual approval file content.
|
109
|
+
def expected!
|
110
|
+
File.exist?(approval_file) ? File.read(approval_file) : ''
|
111
|
+
end
|
112
|
+
|
113
|
+
# Do the actual test.
|
114
|
+
# - If .before() was used, we foreward the actual output to the
|
115
|
+
# proc for processing first.
|
116
|
+
# - If before_approval proc was configured, forward the acual output
|
117
|
+
# to the proc for processing.
|
118
|
+
# - If .diff() was used, then distance will be set and then
|
119
|
+
# we "levenshtein it".
|
120
|
+
# - Otherwise, compare with ==
|
121
|
+
def strings_match?
|
122
|
+
if @before
|
123
|
+
@before.each do |proc|
|
124
|
+
@actual = proc.call actual
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
if RSpec.configuration.before_approval.is_a? Proc
|
129
|
+
@actual = RSpec.configuration.before_approval.call actual
|
130
|
+
end
|
131
|
+
|
132
|
+
if distance
|
133
|
+
@actual_distance = String::Similarity.levenshtein_distance expected, actual
|
134
|
+
@actual_distance <= distance
|
135
|
+
else
|
136
|
+
actual == expected
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns the input string stripped of ANSI escape codes if the
|
141
|
+
# strip_ansi_escape configuration setting was set to true
|
142
|
+
def sanitize(string)
|
143
|
+
sanitize? ? Strings::ANSI.sanitize(string) : string
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'string-similarity'
|
2
|
+
|
3
|
+
module RSpecApprovals
|
4
|
+
module Matchers
|
5
|
+
# Adds the matcher to RSpec:
|
6
|
+
# `expect(string).to match_approval(file)`
|
7
|
+
def match_approval(expected)
|
8
|
+
MatchApproval.new expected
|
9
|
+
end
|
10
|
+
|
11
|
+
class MatchApproval < Base
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module RSpecApprovals
|
2
|
+
module Matchers
|
3
|
+
# Adds the matcher to RSpec:
|
4
|
+
# `expect { stream }.to output_approval(file)`
|
5
|
+
def output_approval(expected)
|
6
|
+
OutputApproval.new expected
|
7
|
+
end
|
8
|
+
|
9
|
+
class OutputApproval < Base
|
10
|
+
# Called by RSpec
|
11
|
+
def matches?(block)
|
12
|
+
return false unless block.is_a? Proc
|
13
|
+
@actual = stream_capturer.capture block
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
# Lets RSpec know that this matcher requires a block.
|
18
|
+
def supports_block_expectations?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
# Adds chained matcher, to allow:
|
23
|
+
# expect { stream }.to output_approval(file).to_stdout
|
24
|
+
# This is the default, and only provided for completeness.
|
25
|
+
def to_stdout
|
26
|
+
@stream_capturer = Stream::Stdout
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
# Adds chained matcher, to allow:
|
31
|
+
# expect { stream }.to output_approval(file).to_stderr
|
32
|
+
def to_stderr
|
33
|
+
@stream_capturer = Stream::Stderr
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def stream_capturer
|
38
|
+
@stream_capturer ||= Stream::Stdout
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module RSpecApprovals
|
2
|
+
module Matchers
|
3
|
+
# Adds the matcher to RSpec:
|
4
|
+
# `expect { something_that_errors }.to raise_approval(file)`
|
5
|
+
def raise_approval(expected)
|
6
|
+
RaiseApproval.new expected
|
7
|
+
end
|
8
|
+
|
9
|
+
class RaiseApproval < Base
|
10
|
+
# Called by RSpec
|
11
|
+
def matches?(block)
|
12
|
+
return false unless block.is_a? Proc
|
13
|
+
@actual = 'Nothing raised'
|
14
|
+
|
15
|
+
begin
|
16
|
+
block.call
|
17
|
+
rescue => e
|
18
|
+
@actual = e.inspect
|
19
|
+
end
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
# Lets RSpec know that this matcher requires a block.
|
25
|
+
def supports_block_expectations?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Add our custom matchers and configuration options to RSpec
|
2
|
+
if defined? RSpec
|
3
|
+
# Fix for rails
|
4
|
+
require 'rspec/core' unless RSpec.respond_to? :configure
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
config.include RSpecApprovals::Matchers
|
8
|
+
config.add_setting :approvals_path, default: File.expand_path('spec/approvals')
|
9
|
+
config.add_setting :interactive_approvals, default: (!ENV['CI'] and !ENV['GITHUB_ACTIONS'])
|
10
|
+
config.add_setting :auto_approve, default: ENV['AUTO_APPROVE']
|
11
|
+
config.add_setting :strip_ansi_escape, default: false
|
12
|
+
config.add_setting :before_approval, default: nil
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'strings-ansi'
|
2
|
+
|
3
|
+
module RSpecApprovals
|
4
|
+
# Capture stdout and stderr
|
5
|
+
#
|
6
|
+
# These methods are borrowed from rspec's built in matchers
|
7
|
+
# https://github.com/rspec/rspec-expectations/blob/add9b271ecb1d65f7da5bc8a9dd8c64d81d92303/lib/rspec/matchers/built_in/output.rb
|
8
|
+
module Stream
|
9
|
+
module Stdout
|
10
|
+
def self.capture(block)
|
11
|
+
RSpecApprovals.stdout.truncate 0
|
12
|
+
RSpecApprovals.stdout.rewind
|
13
|
+
|
14
|
+
original_stream = $stdout
|
15
|
+
$stdout = RSpecApprovals.stdout
|
16
|
+
block.call
|
17
|
+
RSpecApprovals.stdout.string.dup
|
18
|
+
|
19
|
+
ensure
|
20
|
+
$stdout = original_stream
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Stderr
|
26
|
+
def self.capture(block)
|
27
|
+
RSpecApprovals.stderr.truncate 0
|
28
|
+
RSpecApprovals.stderr.rewind
|
29
|
+
|
30
|
+
original_stream = $stderr
|
31
|
+
$stderr = RSpecApprovals.stderr
|
32
|
+
block.call
|
33
|
+
RSpecApprovals.stderr.string.dup
|
34
|
+
|
35
|
+
ensure
|
36
|
+
$stderr = original_stream
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rspec_approvals
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.0.rc1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Danny Ben Shitrit
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-05-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colsole
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: string-similarity
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: diffy
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.3'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: tty-prompt
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.19'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.19'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: strings-ansi
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.1'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.1'
|
83
|
+
description: Automatic interactive approvals for rspec
|
84
|
+
email: db@dannyben.com
|
85
|
+
executables: []
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files: []
|
88
|
+
files:
|
89
|
+
- README.md
|
90
|
+
- lib/rspec_approvals.rb
|
91
|
+
- lib/rspec_approvals/approval_handler.rb
|
92
|
+
- lib/rspec_approvals/extensions/file.rb
|
93
|
+
- lib/rspec_approvals/matchers/base.rb
|
94
|
+
- lib/rspec_approvals/matchers/match_approval.rb
|
95
|
+
- lib/rspec_approvals/matchers/output_approval.rb
|
96
|
+
- lib/rspec_approvals/matchers/raise_approval.rb
|
97
|
+
- lib/rspec_approvals/module_functions.rb
|
98
|
+
- lib/rspec_approvals/rspec_config.rb
|
99
|
+
- lib/rspec_approvals/stream.rb
|
100
|
+
- lib/rspec_approvals/version.rb
|
101
|
+
homepage: https://github.com/DannyBen/rspec_approvals
|
102
|
+
licenses:
|
103
|
+
- MIT
|
104
|
+
metadata: {}
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: 2.3.0
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 1.3.1
|
119
|
+
requirements: []
|
120
|
+
rubygems_version: 3.1.2
|
121
|
+
signing_key:
|
122
|
+
specification_version: 4
|
123
|
+
summary: Interactive RSpec Approvals
|
124
|
+
test_files: []
|