kokishin 0.1.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/.ci/Gemfile.rails_4.0.0 +19 -0
- data/.ci/Gemfile.rails_4.0.0.lock +90 -0
- data/.ci/Gemfile.rails_4.1.0 +18 -0
- data/.ci/Gemfile.rails_4.1.0.lock +93 -0
- data/.ci/Gemfile.rails_4.2.0 +19 -0
- data/.ci/Gemfile.rails_4.2.0.lock +97 -0
- data/.ci/Gemfile.rails_5.0.0 +19 -0
- data/.ci/Gemfile.rails_5.0.0.lock +95 -0
- data/.ci/Gemfile.rails_5.1.0 +19 -0
- data/.ci/Gemfile.rails_5.1.0.lock +97 -0
- data/.ci/Gemfile.rails_5.2.0 +19 -0
- data/.ci/Gemfile.rails_5.2.0.lock +97 -0
- data/.ci/Gemfile.rails_6.0.0 +19 -0
- data/.ci/Gemfile.rails_6.0.0.lock +97 -0
- data/.ci/Gemfile.rails_6.1.0 +19 -0
- data/.ci/Gemfile.rails_6.1.0.lock +96 -0
- data/.ci/Gemfile.rails_7.0.0 +19 -0
- data/.ci/Gemfile.rails_7.0.0.lock +110 -0
- data/.gitignore +9 -0
- data/.gitlab-ci.yml +79 -0
- data/.rspec +1 -0
- data/.rubocop.yml +27 -0
- data/.rubocop_todo.yml +24 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +110 -0
- data/LICENSE.txt +21 -0
- data/README.md +14 -0
- data/Rakefile +8 -0
- data/bin/console +8 -0
- data/bin/setup +8 -0
- data/kokishin.gemspec +32 -0
- data/lib/kokishin/extensions/hash_diff.rb +21 -0
- data/lib/kokishin/matchers/association_matcher.rb +160 -0
- data/lib/kokishin/matchers/method_matcher.rb +53 -0
- data/lib/kokishin/matchers/validate_matcher.rb +90 -0
- data/lib/kokishin/should.rb +24 -0
- data/lib/kokishin/version.rb +7 -0
- data/lib/kokishin.rb +6 -0
- metadata +98 -0
data/kokishin.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'kokishin/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.authors = %w[ bm5k ]
|
9
|
+
spec.bindir = 'exe'
|
10
|
+
spec.description = 'A single home for various custom RSpec matchers.'
|
11
|
+
spec.email = %w[ 226882-BM5k@users.noreply.gitlab.com ]
|
12
|
+
spec.homepage = 'https://gitlab.com/devfu/kokishin'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
spec.name = 'kokishin'
|
15
|
+
spec.require_paths = %w[ lib ]
|
16
|
+
spec.summary = 'RSpec matchers'
|
17
|
+
spec.version = Kokishin::VERSION
|
18
|
+
|
19
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
20
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
21
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
26
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
27
|
+
end
|
28
|
+
|
29
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
30
|
+
|
31
|
+
spec.add_dependency 'rspec-expectations'
|
32
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kokishin
|
4
|
+
module HashDiff
|
5
|
+
|
6
|
+
refine Hash do
|
7
|
+
# Stolen from Rails 4.0.5 since this has been removed!
|
8
|
+
#
|
9
|
+
# Returns a hash that represents the difference between two hashes.
|
10
|
+
#
|
11
|
+
# {1 => 2}.diff(1 => 2) # => {}
|
12
|
+
# {1 => 2}.diff(1 => 3) # => {1 => 2}
|
13
|
+
# {}.diff(1 => 2) # => {1 => 2}
|
14
|
+
# {1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4}
|
15
|
+
def diff other
|
16
|
+
dup.delete_if { |k, v| other[k] == v }.merge!(other.dup.delete_if { |k, _v| key?(k) })
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kokishin/extensions/hash_diff'
|
4
|
+
|
5
|
+
using Kokishin::HashDiff
|
6
|
+
|
7
|
+
RSpec::Matchers.define :belong_to do |kind|
|
8
|
+
description do
|
9
|
+
"belong_to #{ kind.inspect }"
|
10
|
+
end
|
11
|
+
|
12
|
+
failure_message do
|
13
|
+
msg = "Expected #{ described_class } to belong_to #{ kind.inspect }"
|
14
|
+
msg << "\n column #{ key.inspect } does not exist in #{ described_class }" if associated? && !has_column?
|
15
|
+
msg
|
16
|
+
end
|
17
|
+
|
18
|
+
failure_message_when_negated do
|
19
|
+
"Expected #{ described_class } not to belong_to #{ kind.inspect }"
|
20
|
+
end
|
21
|
+
|
22
|
+
def association
|
23
|
+
gem_version = Gem::Version.new ActiveRecord::VERSION::STRING
|
24
|
+
|
25
|
+
key = if gem_version >= Gem::Version.new('4.2.0')
|
26
|
+
expected.to_s
|
27
|
+
else
|
28
|
+
expected
|
29
|
+
end
|
30
|
+
|
31
|
+
actual.class.reflections[key]
|
32
|
+
end
|
33
|
+
|
34
|
+
def associated?
|
35
|
+
association && association.macro == :belongs_to
|
36
|
+
end
|
37
|
+
|
38
|
+
def key
|
39
|
+
@key ||= association.foreign_key
|
40
|
+
end
|
41
|
+
|
42
|
+
def has_column?
|
43
|
+
actual.class.column_names.include? key
|
44
|
+
end
|
45
|
+
|
46
|
+
match do |_actual|
|
47
|
+
associated? && has_column?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
RSpec::Matchers.define :have_many do |kind, expected_options|
|
52
|
+
description do
|
53
|
+
msg = "have_many #{ kind.inspect }"
|
54
|
+
msg << ", #{ @expected_options.map { |k, v| ":#{ k } => #{ v }" }.join ',' }" if @expected_options.present?
|
55
|
+
|
56
|
+
@expected_options = nil # for some reason this appears to be cached between runs?
|
57
|
+
|
58
|
+
msg
|
59
|
+
end
|
60
|
+
|
61
|
+
failure_message do
|
62
|
+
"Expected #{ described_class } to have_many #{ kind.inspect } #{ diff }"
|
63
|
+
end
|
64
|
+
|
65
|
+
failure_message_when_negated do
|
66
|
+
"Expected #{ described_class } not to have_many #{ kind.inspect } #{ diff }"
|
67
|
+
end
|
68
|
+
|
69
|
+
def expected_options
|
70
|
+
@expected_options || {}
|
71
|
+
end
|
72
|
+
|
73
|
+
def association
|
74
|
+
@association ||= actual.class.reflect_on_association [ *expected ].first
|
75
|
+
end
|
76
|
+
|
77
|
+
def association_options
|
78
|
+
@association_options ||= association.options.reject { |k, v| k == :extend and v == [] } rescue {}
|
79
|
+
end
|
80
|
+
|
81
|
+
def diff?
|
82
|
+
# if association_options is empty and @expected_options is not, there is a problem
|
83
|
+
# if @expected_options is empty and association_options is not, there is a problem
|
84
|
+
# if neither is empty diff them, if there's a diff, there is a problem
|
85
|
+
|
86
|
+
return true if expected_options.present? ^ association_options.present?
|
87
|
+
return true if association_options.diff(expected_options).present?
|
88
|
+
|
89
|
+
false
|
90
|
+
end
|
91
|
+
|
92
|
+
def diff
|
93
|
+
return '' unless diff?
|
94
|
+
|
95
|
+
<<-MSG
|
96
|
+
\n expected options: #{ expected_options.inspect }
|
97
|
+
\n actual options: #{ association_options.inspect }
|
98
|
+
MSG
|
99
|
+
end
|
100
|
+
|
101
|
+
match do |_actual|
|
102
|
+
@expected_options = expected_options
|
103
|
+
association.try(:macro) == :has_many and not diff?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
RSpec::Matchers.define :have_one do |kind, expected_options|
|
108
|
+
description do
|
109
|
+
msg = "have_many #{ kind.inspect }"
|
110
|
+
msg << ", #{ @expected_options.map { |k, v| ":#{ k } => #{ v }" }.join ',' }" if @expected_options.present?
|
111
|
+
|
112
|
+
@expected_options = nil # for some reason this appears to be cached between runs?
|
113
|
+
|
114
|
+
msg
|
115
|
+
end
|
116
|
+
|
117
|
+
failure_message do
|
118
|
+
"Expected #{ described_class } to have_many #{ kind.inspect } #{ diff }"
|
119
|
+
end
|
120
|
+
|
121
|
+
failure_message_when_negated do
|
122
|
+
"Expected #{ described_class } not to have_many #{ kind.inspect } #{ diff }"
|
123
|
+
end
|
124
|
+
|
125
|
+
def expected_options
|
126
|
+
@expected_options || {}
|
127
|
+
end
|
128
|
+
|
129
|
+
def association
|
130
|
+
@association ||= actual.class.reflect_on_association [ *expected ].first
|
131
|
+
end
|
132
|
+
|
133
|
+
def association_options
|
134
|
+
@association_options ||= association.options.reject { |k, v| k == :extend and v == [] } rescue {}
|
135
|
+
end
|
136
|
+
|
137
|
+
def diff?
|
138
|
+
# if association_options is empty and @expected_options is not, there is a problem
|
139
|
+
# if @expected_options is empty and association_options is not, there is a problem
|
140
|
+
# if neither is empty diff them, if there's a diff, there is a problem
|
141
|
+
return true if expected_options.present? ^ association_options.present?
|
142
|
+
return true if association_options.diff(expected_options).present?
|
143
|
+
|
144
|
+
false
|
145
|
+
end
|
146
|
+
|
147
|
+
def diff
|
148
|
+
return '' unless diff?
|
149
|
+
|
150
|
+
<<-MSG
|
151
|
+
\n expected options: #{ expected_options.inspect }
|
152
|
+
\n actual options: #{ association_options.inspect }
|
153
|
+
MSG
|
154
|
+
end
|
155
|
+
|
156
|
+
match do |_actual|
|
157
|
+
@expected_options = expected_options
|
158
|
+
association.try(:macro) == :has_one and not diff?
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/expectations'
|
4
|
+
|
5
|
+
RSpec::Matchers.define :have_method do |method_name, include_all = false|
|
6
|
+
match do |actual|
|
7
|
+
@actual = actual
|
8
|
+
@include_all = include_all
|
9
|
+
@method_name = method_name
|
10
|
+
|
11
|
+
if @alias_name
|
12
|
+
responds_to_method? && responds_to_alias? && source_locations_match?
|
13
|
+
else
|
14
|
+
responds_to_method?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
chain :with_alias do |alias_name|
|
19
|
+
@alias_name = alias_name
|
20
|
+
end
|
21
|
+
|
22
|
+
description do
|
23
|
+
if @alias_name
|
24
|
+
"have method ##{ @method_name } with alias ##{ @alias_name }"
|
25
|
+
else
|
26
|
+
"have method ##{ @method_name }"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
failure_message do
|
31
|
+
"should have method ##{ @method_name }, but does not."
|
32
|
+
end
|
33
|
+
|
34
|
+
failure_message_when_negated do
|
35
|
+
"should not have method ##{ @method_name }, but does."
|
36
|
+
end
|
37
|
+
|
38
|
+
def responds_to_alias?
|
39
|
+
subject.respond_to? @alias_name, @include_all
|
40
|
+
end
|
41
|
+
|
42
|
+
def responds_to_method?
|
43
|
+
subject.respond_to? @method_name, @include_all
|
44
|
+
end
|
45
|
+
|
46
|
+
def source_locations_match?
|
47
|
+
location_of(@method_name) == location_of(@alias_name)
|
48
|
+
end
|
49
|
+
|
50
|
+
def location_of name
|
51
|
+
@actual.method(name).source_location
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/expectations'
|
4
|
+
|
5
|
+
RSpec::Matchers.define :validate do |_expected|
|
6
|
+
chain(:of) { |attr| @attribute = attr }
|
7
|
+
|
8
|
+
chain(:with) do |*opts|
|
9
|
+
@options = opts.extract_options!
|
10
|
+
@options = opts if @options.empty?
|
11
|
+
end
|
12
|
+
|
13
|
+
match do |_actual|
|
14
|
+
if @attribute
|
15
|
+
validator.present? && (@options ? validator.options.should =~ @options : true)
|
16
|
+
else
|
17
|
+
callback.present? && (@options ? check_callback_options : true)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
match_when_negated do |_actual|
|
22
|
+
if @attribute
|
23
|
+
raise "Negating a matcher with options doesn't really make sense!" if @options
|
24
|
+
|
25
|
+
validator.should be_nil
|
26
|
+
else
|
27
|
+
callback.should be_nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def callback
|
32
|
+
actual.class._validate_callbacks.detect { |c| c.filter == expected }
|
33
|
+
end
|
34
|
+
|
35
|
+
def check_callback_options
|
36
|
+
opts = parse_callback_options
|
37
|
+
|
38
|
+
if @options.is_a? Hash
|
39
|
+
raise 'Cannot expect custom procs' if @options.values.any? { |v| v.is_a? Proc }
|
40
|
+
|
41
|
+
@options.each { |k, _| opts[k].should =~ Array(@options[k]) }
|
42
|
+
else
|
43
|
+
opts.keys.should =~ @options
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse_callback_options
|
48
|
+
gem_version = Gem::Version.new ActiveModel::VERSION::STRING
|
49
|
+
|
50
|
+
options = if (Gem::Version.new('4.0.0')...Gem::Version.new('4.1.0')).cover?(gem_version)
|
51
|
+
parse_callback_options4
|
52
|
+
else
|
53
|
+
parse_callback_options_default
|
54
|
+
end
|
55
|
+
|
56
|
+
options.reject { |_, v| v.empty? }
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_callback_options_default
|
60
|
+
ifs = callback.instance_variable_get '@if'
|
61
|
+
unlesses = callback.instance_variable_get '@unless'
|
62
|
+
|
63
|
+
if ifs.first.is_a?(Proc)
|
64
|
+
proc_binding = ifs.first.binding
|
65
|
+
if proc_binding.local_variables.include? :context # rails 7.1.0+
|
66
|
+
ons = proc_binding.local_variable_get :context
|
67
|
+
ifs = ifs[1..-1]
|
68
|
+
elsif proc_binding.local_variables.include? :options
|
69
|
+
ons = proc_binding.local_variable_get(:options)[:on]
|
70
|
+
ifs = ifs[1..-1]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
{ if: ifs, on: Array(ons), unless: Array(unlesses) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse_callback_options4
|
78
|
+
ifs = [ *callback.options[:if] ]
|
79
|
+
ons = [ *callback.options[:on] ]
|
80
|
+
unlesses = [ *callback.options[:unless] ]
|
81
|
+
|
82
|
+
ifs = ifs[1..-1] if ons.any?
|
83
|
+
|
84
|
+
{ if: ifs, on: ons, unless: unlesses }
|
85
|
+
end
|
86
|
+
|
87
|
+
def validator
|
88
|
+
actual.class.validators_on(@attribute).detect { |v| v.kind == expected }
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Should syntax is the best syntax.
|
4
|
+
require 'rspec/matchers'
|
5
|
+
|
6
|
+
module RSpec
|
7
|
+
|
8
|
+
configure do |config|
|
9
|
+
config.expect_with(:rspec) { |spec| spec.syntax = :should }
|
10
|
+
config.mock_with(:rspec) { |spec| spec.syntax = :should }
|
11
|
+
end
|
12
|
+
|
13
|
+
module Matchers
|
14
|
+
|
15
|
+
# Force generated description to use should syntax
|
16
|
+
def self.generated_description
|
17
|
+
return nil if last_expectation_handler.nil?
|
18
|
+
|
19
|
+
"should #{ last_description }"
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/kokishin.rb
ADDED
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kokishin
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- bm5k
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-10-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec-expectations
|
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
|
+
description: A single home for various custom RSpec matchers.
|
28
|
+
email:
|
29
|
+
- 226882-BM5k@users.noreply.gitlab.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".ci/Gemfile.rails_4.0.0"
|
35
|
+
- ".ci/Gemfile.rails_4.0.0.lock"
|
36
|
+
- ".ci/Gemfile.rails_4.1.0"
|
37
|
+
- ".ci/Gemfile.rails_4.1.0.lock"
|
38
|
+
- ".ci/Gemfile.rails_4.2.0"
|
39
|
+
- ".ci/Gemfile.rails_4.2.0.lock"
|
40
|
+
- ".ci/Gemfile.rails_5.0.0"
|
41
|
+
- ".ci/Gemfile.rails_5.0.0.lock"
|
42
|
+
- ".ci/Gemfile.rails_5.1.0"
|
43
|
+
- ".ci/Gemfile.rails_5.1.0.lock"
|
44
|
+
- ".ci/Gemfile.rails_5.2.0"
|
45
|
+
- ".ci/Gemfile.rails_5.2.0.lock"
|
46
|
+
- ".ci/Gemfile.rails_6.0.0"
|
47
|
+
- ".ci/Gemfile.rails_6.0.0.lock"
|
48
|
+
- ".ci/Gemfile.rails_6.1.0"
|
49
|
+
- ".ci/Gemfile.rails_6.1.0.lock"
|
50
|
+
- ".ci/Gemfile.rails_7.0.0"
|
51
|
+
- ".ci/Gemfile.rails_7.0.0.lock"
|
52
|
+
- ".gitignore"
|
53
|
+
- ".gitlab-ci.yml"
|
54
|
+
- ".rspec"
|
55
|
+
- ".rubocop.yml"
|
56
|
+
- ".rubocop_todo.yml"
|
57
|
+
- Gemfile
|
58
|
+
- Gemfile.lock
|
59
|
+
- LICENSE.txt
|
60
|
+
- README.md
|
61
|
+
- Rakefile
|
62
|
+
- bin/console
|
63
|
+
- bin/setup
|
64
|
+
- kokishin.gemspec
|
65
|
+
- lib/kokishin.rb
|
66
|
+
- lib/kokishin/extensions/hash_diff.rb
|
67
|
+
- lib/kokishin/matchers/association_matcher.rb
|
68
|
+
- lib/kokishin/matchers/method_matcher.rb
|
69
|
+
- lib/kokishin/matchers/validate_matcher.rb
|
70
|
+
- lib/kokishin/should.rb
|
71
|
+
- lib/kokishin/version.rb
|
72
|
+
homepage: https://gitlab.com/devfu/kokishin
|
73
|
+
licenses:
|
74
|
+
- MIT
|
75
|
+
metadata:
|
76
|
+
homepage_uri: https://gitlab.com/devfu/kokishin
|
77
|
+
rubygems_mfa_required: 'true'
|
78
|
+
source_code_uri: https://gitlab.com/devfu/kokishin
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubygems_version: 3.4.10
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: RSpec matchers
|
98
|
+
test_files: []
|