rspec-inline-snapshot 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/rspec/inline_snapshot/matchers.rb +184 -0
- data/lib/rspec/inline_snapshot/version.rb +18 -0
- data/lib/rspec-inline-snapshot.rb +21 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 01661d8bb47e00451b851af3d426cc0bc2e0dd5a1ad6a087b2bf036fe0260fd2
|
4
|
+
data.tar.gz: d9bcd6ddb63f4b6234e29fe10933abc60e13c4e8fc73573125d04cee83c0668b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 50ff7415887cf95c6f04cf7e5d1655fcd984f70f9673f9eab20c650a45a1427e52328c9f6642621d9dbb6e8baef752557d4c6988003d04958d6d10e8433f9c8f
|
7
|
+
data.tar.gz: 0b38196c74116b3a432924c12aaab36fb2f81a010eb62b7d96a094235e5dd839bc495aa22906c0fa4403b9184aac9418ab0419ca40970470bb86bce7c46e45dd
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Copyright 2022 Hummingbird RegTech, Inc.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
module RSpec
|
17
|
+
module InlineSnapshot
|
18
|
+
module Matchers
|
19
|
+
extend RSpec::Matchers::DSL
|
20
|
+
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.after(:suite) do
|
23
|
+
REWRITERS.each do |source_file_path, (_parsed_source, corrector)|
|
24
|
+
File.write(source_file_path, corrector.process)
|
25
|
+
end
|
26
|
+
|
27
|
+
REWRITERS.clear
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
UNDEFINED_EXPECTED_VALUE = :_inline_snapshot_undefined
|
32
|
+
# FIXME: there's probably a way to do this without abusing a constant but it works for now
|
33
|
+
# file => [parsed_source, corrector]
|
34
|
+
REWRITERS = {}
|
35
|
+
|
36
|
+
FALSE_VALUES = [
|
37
|
+
nil,
|
38
|
+
'',
|
39
|
+
'0',
|
40
|
+
'f',
|
41
|
+
'false',
|
42
|
+
'off'
|
43
|
+
].to_set.freeze
|
44
|
+
|
45
|
+
# This is fiddly. The AST is a bit annoying in that the #location and/or #source_range of
|
46
|
+
# a SendNode are only the expression, and do not consistently include recursive children in the range.
|
47
|
+
# So we have to do the math ourselves... case by case. We aim for the range to start
|
48
|
+
# at the open parentheses (if there is one) and end at the close parentheses or heredoc terminator.
|
49
|
+
def replacement_range(node, parsed_source)
|
50
|
+
matcher_arg = node.arguments.last
|
51
|
+
|
52
|
+
if matcher_arg.nil? && node.location.begin.nil?
|
53
|
+
# no-args command-style call. ie. "match_inline_snapshot"
|
54
|
+
Parser::Source::Range.new(
|
55
|
+
parsed_source.buffer,
|
56
|
+
node.location.expression.end_pos,
|
57
|
+
node.location.expression.end_pos
|
58
|
+
)
|
59
|
+
elsif matcher_arg.is_a?(RuboCop::AST::StrNode) && matcher_arg.heredoc?
|
60
|
+
# with heredoc parameter ie. "match_inline_snapshot(<<~SNAP) ... SNAP"
|
61
|
+
Parser::Source::Range.new(
|
62
|
+
parsed_source.buffer,
|
63
|
+
node.location.begin.begin_pos,
|
64
|
+
node.arguments.last.location.heredoc_end.end_pos
|
65
|
+
)
|
66
|
+
elsif matcher_arg.is_a?(RuboCop::AST::SendNode) &&
|
67
|
+
matcher_arg.receiver.heredoc? &&
|
68
|
+
matcher_arg.method_name == :chomp
|
69
|
+
# with chomped heredoc parameter ie. "match_inline_snapshot(<<~SNAP.chomp) ... SNAP"
|
70
|
+
Parser::Source::Range.new(
|
71
|
+
parsed_source.buffer,
|
72
|
+
node.location.begin.begin_pos,
|
73
|
+
matcher_arg.receiver.location.heredoc_end.end_pos
|
74
|
+
)
|
75
|
+
else
|
76
|
+
# no-args call with parens. ie. "match_inline_snapshot()"
|
77
|
+
# or with a plain old parameter ie. "match_inline_snapshot('foo')"
|
78
|
+
Parser::Source::Range.new(
|
79
|
+
parsed_source.buffer,
|
80
|
+
node.location.begin.begin_pos,
|
81
|
+
node.location.end.end_pos
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Format the actual value so it can be injected into the source as the new argument.
|
87
|
+
def format_replacement(actual, node, parsed_source)
|
88
|
+
if actual.is_a?(String)
|
89
|
+
if actual.include?("\n")
|
90
|
+
indent = parsed_source.line_indentation(node.location.first_line)
|
91
|
+
[
|
92
|
+
'(<<~SNAP.chomp)',
|
93
|
+
actual.split("\n", -1).map { |line| "#{' ' * (indent + 2)}#{line}" },
|
94
|
+
"#{' ' * indent}SNAP"
|
95
|
+
].join("\n")
|
96
|
+
else
|
97
|
+
"(#{actual.inspect})"
|
98
|
+
end
|
99
|
+
elsif actual.is_a?(NilClass) || actual.is_a?(Integer) || actual.is_a?(TrueClass) || actual.is_a?(FalseClass)
|
100
|
+
"(#{actual.inspect})"
|
101
|
+
elsif actual.respond_to?(:as_json)
|
102
|
+
"(#{actual.as_json.inspect})"
|
103
|
+
else
|
104
|
+
raise ArgumentError,
|
105
|
+
"Cannot snapshot. Actual (#{actual.class}) is not a String and does not implement #as_json"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def should_update_inline_snapshot?(expected)
|
110
|
+
env_to_boolean(ENV['UPDATE_MATCH_SNAPSHOT']) ||
|
111
|
+
env_to_boolean(ENV['UPDATE_SNAPSHOTS']) ||
|
112
|
+
expected == UNDEFINED_EXPECTED_VALUE
|
113
|
+
end
|
114
|
+
|
115
|
+
def running_in_ci?
|
116
|
+
env_to_boolean(ENV['CI'])
|
117
|
+
end
|
118
|
+
|
119
|
+
def match_or_update_inline_snapshot(matcher_name, expected, actual)
|
120
|
+
if should_update_inline_snapshot?(expected)
|
121
|
+
return false if running_in_ci?
|
122
|
+
|
123
|
+
# General algorithm:
|
124
|
+
# 1. Get location. RSpec.current_example.location
|
125
|
+
# 2. Crawl Kernel.caller_locations until first hit at #absolute_path from 1
|
126
|
+
# 3. Use #lineno as heuristic to find call to :match_inline_snapshot (matcher_name) in AST
|
127
|
+
# 4. Rewrite first argument of method call
|
128
|
+
source_file_path = RSpec.current_example.metadata[:absolute_file_path]
|
129
|
+
caller_location = Kernel.caller_locations.detect do |cl|
|
130
|
+
cl.absolute_path == RSpec.current_example.metadata[:absolute_file_path]
|
131
|
+
end
|
132
|
+
matcher_call_line_number = caller_location.lineno
|
133
|
+
|
134
|
+
# Parse the spec file.
|
135
|
+
# See:
|
136
|
+
# https://www.rubydoc.info/github/whitequark/parser/Parser/TreeRewriter
|
137
|
+
# https://medium.com/flippengineering/using-rubocop-ast-to-transform-ruby-files-using-abstract-syntax-trees-3e352e9ac916
|
138
|
+
REWRITERS[source_file_path] ||= begin
|
139
|
+
parsed_source = RuboCop::AST::ProcessedSource.from_file(source_file_path,
|
140
|
+
2.7)
|
141
|
+
corrector = ::RuboCop::Cop::Corrector.new(parsed_source)
|
142
|
+
[parsed_source, corrector]
|
143
|
+
end
|
144
|
+
|
145
|
+
parsed_source, corrector = REWRITERS[source_file_path]
|
146
|
+
|
147
|
+
parsed_source.ast.each_node(:send) do |node|
|
148
|
+
next unless node.location.first_line >= matcher_call_line_number && node.method_name == matcher_name
|
149
|
+
|
150
|
+
# found it! well we hope anyway because we're about to blow something away!
|
151
|
+
corrector.replace(replacement_range(node, parsed_source), format_replacement(actual, node, parsed_source))
|
152
|
+
|
153
|
+
return true # we replaced the target argument and overwrote the file. no point going further in this spec file.
|
154
|
+
end
|
155
|
+
raise "possible bug in inline snapshot matcher. Did not locate call to #{matcher_name} in #{source_file_path}"
|
156
|
+
else
|
157
|
+
RSpec::Support::FuzzyMatcher.values_match?(expected, actual)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def env_to_boolean(value)
|
162
|
+
!FALSE_VALUES.include?(value&.downcase)
|
163
|
+
end
|
164
|
+
|
165
|
+
matcher :match_inline_snapshot do |expected = UNDEFINED_EXPECTED_VALUE|
|
166
|
+
diffable
|
167
|
+
|
168
|
+
match do |actual|
|
169
|
+
match_or_update_inline_snapshot(:match_inline_snapshot, expected, actual)
|
170
|
+
end
|
171
|
+
|
172
|
+
failure_message do |_actual|
|
173
|
+
if running_in_ci? && should_update_inline_snapshot?(expected)
|
174
|
+
'cannot update snapshots in CI. Did you forget to check it in?'
|
175
|
+
else
|
176
|
+
# NB: yes this works.
|
177
|
+
# The failure_message macro gets turned into a method definition by rspec
|
178
|
+
super()
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Copyright 2022 Hummingbird RegTech, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
module RSpec
|
15
|
+
module InlineSnapshot
|
16
|
+
VERSION = '1.0.0'.freeze
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Copyright 2022 Hummingbird RegTech, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
require 'rubocop/ast'
|
15
|
+
require 'rubocop/cop/corrector'
|
16
|
+
|
17
|
+
require_relative 'rspec/inline_snapshot/matchers'
|
18
|
+
|
19
|
+
RSpec.configure do |config|
|
20
|
+
config.include RSpec::InlineSnapshot::Matchers
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rspec-inline-snapshot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hummingbird RegTech, Inc.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-12-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubocop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop-ast
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Inline snapshot expectations for RSpec
|
70
|
+
email: info@hummingbird.co
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- lib/rspec-inline-snapshot.rb
|
76
|
+
- lib/rspec/inline_snapshot/matchers.rb
|
77
|
+
- lib/rspec/inline_snapshot/version.rb
|
78
|
+
homepage: https://github.com/Hummingbird-RegTech/rspec-inline-snapshot
|
79
|
+
licenses:
|
80
|
+
- Apache-2.0
|
81
|
+
metadata:
|
82
|
+
rubygems_mfa_required: 'true'
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: 2.7.0
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubygems_version: 3.1.6
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: Inline snapshot expectations for RSpec
|
102
|
+
test_files: []
|