rspec-inline-snapshot 1.0.0
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 +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: []
|