rao-shoulda_matchers 0.0.13.pre
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/MIT-LICENSE +20 -0
- data/Rakefile +27 -0
- data/lib/generators/rao/shoulda_matchers/resources_controller_generator.rb +55 -0
- data/lib/generators/rao/shoulda_matchers/templates/resources_controller_spec.rb +53 -0
- data/lib/rao/shoulda/matchers/implement_create_action_matcher.rb +161 -0
- data/lib/rao/shoulda/matchers/implement_delete_action_matcher.rb +115 -0
- data/lib/rao/shoulda/matchers/implement_index_action_matcher.rb +70 -0
- data/lib/rao/shoulda/matchers/implement_show_action_matcher.rb +81 -0
- data/lib/rao/shoulda/matchers/implement_update_action_matcher.rb +188 -0
- data/lib/rao/shoulda/matchers.rb +12 -0
- data/lib/rao/shoulda_matchers/version.rb +7 -0
- data/lib/rao/shoulda_matchers.rb +14 -0
- data/lib/rao-shoulda_matchers.rb +3 -0
- metadata +154 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5f0735c773f33792ea5d48359b15bc611216be4e457ec09897332e282745faaa
|
4
|
+
data.tar.gz: f18fcc17da38fc31211bfec30e1058d574a2854fbabeeb250ef9460af62c5b4e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 30fd2cd6f4a930f22599a00b25576825918d240edbdd146f2112f4f02269500fec3033acaf8bf9c4f2b897b3cd743fcf2e1bdb65055bf35206b9fe22e31ec91c
|
7
|
+
data.tar.gz: 5444accb9e0bbe8fe4c95b704eab293ef1a29ff07ede9dbefeaf58891e7decc6473b4bec3e97004ed894e1b76b6c62e36013f20acc28622e3e960389f83d55c1
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2018 Roberto Vasquez Angel
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Rao Shoulda Matchers'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
load 'rails/tasks/statistics.rake'
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
require 'bundler/gem_tasks'
|
26
|
+
|
27
|
+
require 'rails/dummy/tasks'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Rao
|
2
|
+
module ShouldaMatchers
|
3
|
+
# This will generate specs for create, read, update, delete and list.
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
#
|
7
|
+
# rails g rao:shoulda_matchers:resources_controller --uri /de/backend/uploads
|
8
|
+
# create spec/features/de/backend/uploads_feature_spec.rb
|
9
|
+
#
|
10
|
+
# If your resource class does not match the last part of your url (i.e.
|
11
|
+
# /de/posts would guess the resource class to Post) you can specify the resource
|
12
|
+
# name like this:
|
13
|
+
#
|
14
|
+
# RESOURCE_CLASS=Blog::Post rails g rails:add_ons:resources_controller_spec --uri /de/posts
|
15
|
+
# create spec/features/de/posts_feature_spec.rb
|
16
|
+
#
|
17
|
+
class ResourcesControllerGenerator < Rails::Generators::Base
|
18
|
+
desc 'Generates CRUDL specs for REST resources'
|
19
|
+
|
20
|
+
source_root File.expand_path('../templates', __FILE__)
|
21
|
+
|
22
|
+
class_option :uri, type: :string, required: true
|
23
|
+
class_option :resource_class, type: :string
|
24
|
+
|
25
|
+
def uri
|
26
|
+
@uri ||= options['uri']
|
27
|
+
end
|
28
|
+
|
29
|
+
def edit_form_dom_selector
|
30
|
+
".edit_#{resource_class.demodulize.underscore}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def new_form_dom_selector
|
34
|
+
"#new_#{resource_class.demodulize.underscore}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def resource_class
|
38
|
+
# @resource_class ||= ENV.fetch('RESOURCE_CLASS') { @uri.split('/').last.camelize.singularize }
|
39
|
+
@resource_class ||= options['resource_class'] ||= @uri.split('/').last.camelize.singularize
|
40
|
+
end
|
41
|
+
|
42
|
+
def factory_name
|
43
|
+
underscored_resource_class
|
44
|
+
end
|
45
|
+
|
46
|
+
def underscored_resource_class
|
47
|
+
@undescored_resource_class ||= resource_class.underscore.gsub('/', '_')
|
48
|
+
end
|
49
|
+
|
50
|
+
def generate_spec
|
51
|
+
template 'resources_controller_spec.rb', "spec/features#{uri}_feature_spec.rb"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
RSpec.describe '<%= uri %>', type: :feature do
|
4
|
+
let(:resource_class) { <%= resource_class %> }
|
5
|
+
let(:resource) { create(:<%= factory_name %>) }
|
6
|
+
let(:resources) { create_list(:<%= factory_name %>, 3) }
|
7
|
+
|
8
|
+
# List
|
9
|
+
it { resources; expect(subject).to implement_index_action(self) }
|
10
|
+
|
11
|
+
# Create
|
12
|
+
it {
|
13
|
+
expect(subject).to implement_create_action(self)
|
14
|
+
.for(resource_class)
|
15
|
+
.within_form('<%= new_form_dom_selector %>') {
|
16
|
+
# fill the needed form inputs via capybara here
|
17
|
+
#
|
18
|
+
# Example:
|
19
|
+
#
|
20
|
+
# select 'de', from: 'slider[locale]'
|
21
|
+
# fill_in 'slider[name]', with: 'My first slider'
|
22
|
+
# check 'slider[auto_start]'
|
23
|
+
# fill_in 'slider[interval]', with: '3'
|
24
|
+
}
|
25
|
+
.increasing{ <%= resource_class %>.count }.by(1)
|
26
|
+
}
|
27
|
+
|
28
|
+
# Read
|
29
|
+
it { expect(subject).to implement_show_action(self).for(resource) }
|
30
|
+
|
31
|
+
# Update
|
32
|
+
it {
|
33
|
+
expect(subject).to implement_update_action(self)
|
34
|
+
.for(resource)
|
35
|
+
.within_form('<%= edit_form_dom_selector %>') {
|
36
|
+
# fill the needed form inputs via capybara here
|
37
|
+
#
|
38
|
+
# Example:
|
39
|
+
#
|
40
|
+
# fill_in 'slider[name]', with: 'New name'
|
41
|
+
}
|
42
|
+
.updating
|
43
|
+
.from(resource.attributes)
|
44
|
+
.to({ }) # Example: .to({ 'name' => 'New name' })
|
45
|
+
}
|
46
|
+
|
47
|
+
# Delete
|
48
|
+
it {
|
49
|
+
expect(subject).to implement_delete_action(self)
|
50
|
+
.for(resource)
|
51
|
+
.reducing{ resource_class.count }.by(1)
|
52
|
+
}
|
53
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
module Rao
|
2
|
+
module Shoulda
|
3
|
+
module Matchers
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# RSpec.describe '/posts', type: :feature do
|
7
|
+
# it {
|
8
|
+
# expect(subject).to implement_create_action(self)
|
9
|
+
# .for(Post)
|
10
|
+
# .within_form('#new_post') {
|
11
|
+
# fill_in 'post[title]', with: 'My first post'
|
12
|
+
# }
|
13
|
+
# .increasing{ |resource_class| resource_class.count }.by(1)
|
14
|
+
# }
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# The newly created resource is found by calling @resource_class.last.
|
18
|
+
# If you need to change this you can call #finding_created_resource_with.
|
19
|
+
#
|
20
|
+
# Example:
|
21
|
+
#
|
22
|
+
# RSpec.describe '/posts', type: :feature do
|
23
|
+
# it {
|
24
|
+
# expect(subject).to implement_create_action(self)
|
25
|
+
# .for(resource_class)
|
26
|
+
# .within_form('#new_user') {
|
27
|
+
# # fill the needed form inputs via capybara here
|
28
|
+
# #
|
29
|
+
# # Example:
|
30
|
+
# #
|
31
|
+
# # select 'de', from: 'slider[locale]'
|
32
|
+
# # fill_in 'slider[name]', with: 'My first slider'
|
33
|
+
# # check 'slider[auto_start]'
|
34
|
+
# # fill_in 'slider[interval]', with: '3'
|
35
|
+
# fill_in 'user[email]', with: 'jane.doe@local.domain'
|
36
|
+
# fill_in 'user[password]', with: 'password'
|
37
|
+
# fill_in 'user[password_confirmation]', with: 'password'
|
38
|
+
# }
|
39
|
+
# .finding_created_resource_with{ Ecm::UserArea::User.order(created_at: :desc).first }
|
40
|
+
# .increasing{ Ecm::UserArea::User.count }.by(1)
|
41
|
+
# }
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
def implement_create_action(spec)
|
45
|
+
ImplementCreateActionMatcher.new(spec)
|
46
|
+
end
|
47
|
+
|
48
|
+
class ImplementCreateActionMatcher
|
49
|
+
include RSpec::Matchers
|
50
|
+
|
51
|
+
def initialize(spec)
|
52
|
+
@spec = spec
|
53
|
+
end
|
54
|
+
|
55
|
+
def id(id)
|
56
|
+
@id = id
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def increasing(&block)
|
61
|
+
@block = block
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def by(expected_increase)
|
66
|
+
@expected_increase = expected_increase
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
# Resource class that will be created
|
71
|
+
def for(resource_class)
|
72
|
+
@resource_class = resource_class
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
# Specifies the form css id to fill to create the resource.
|
77
|
+
def within_form(id, &block)
|
78
|
+
@form_id = id
|
79
|
+
@form_block = block
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
def finding_created_resource_with(&block)
|
84
|
+
@created_resource_finder_block = block
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
def matches?(base_path)
|
89
|
+
@base_path = @spec.class.name.split('::')[0..2].join('::').constantize.description
|
90
|
+
# @base_path = base_path
|
91
|
+
@new_path = "#{@base_path}/new"
|
92
|
+
|
93
|
+
@spec.visit(@new_path)
|
94
|
+
|
95
|
+
@before_count = @block.call(@resource_class)
|
96
|
+
@spec.within(@form_id) do
|
97
|
+
@form_block.call
|
98
|
+
@spec.find('input[name="commit"]').click
|
99
|
+
end
|
100
|
+
@after_count = @block.call(@resource_class)
|
101
|
+
|
102
|
+
has_correct_status_code && has_increased_resource_count && has_correct_current_path
|
103
|
+
end
|
104
|
+
|
105
|
+
def created_resource
|
106
|
+
@created_resource ||= @created_resource_finder_block.present? ? @created_resource_finder_block.call : @resource_class.last
|
107
|
+
end
|
108
|
+
|
109
|
+
def expected_path
|
110
|
+
@expected_path ||= "#{@base_path}/#{created_resource.to_param}"
|
111
|
+
end
|
112
|
+
|
113
|
+
def has_correct_status_code
|
114
|
+
begin
|
115
|
+
if @spec.status_code == 200
|
116
|
+
true
|
117
|
+
else
|
118
|
+
@error = "Wrong status code [#{@spec.status_code}] instead of [200]"
|
119
|
+
false
|
120
|
+
end
|
121
|
+
rescue Capybara::NotSupportedByDriverError => e
|
122
|
+
puts "[Warning] Skipping status code check as it is not supported by your driver [#{@spec.driver.instance_variable_get(:@name)}]."
|
123
|
+
return true
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def has_increased_resource_count
|
128
|
+
if (@after_count - @before_count) == @expected_increase
|
129
|
+
true
|
130
|
+
else
|
131
|
+
@error = "Did not increase by expected [#{@expected_increase}] but by [#{@after_count - @before_count}]"
|
132
|
+
false
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def has_correct_current_path
|
137
|
+
if @spec.current_path == expected_path
|
138
|
+
true
|
139
|
+
else
|
140
|
+
@error = "Wrong current path [#{@spec.current_path}] instead of [#{expected_path}]"
|
141
|
+
false
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def failure_message
|
146
|
+
"Should expose create action on #{@new_path}. Error: #{@error}"
|
147
|
+
end
|
148
|
+
|
149
|
+
def failure_message_when_negated
|
150
|
+
"Should not expose create action on #{@new_path}. Error: #{@error}"
|
151
|
+
end
|
152
|
+
|
153
|
+
alias negative_failure_message failure_message_when_negated
|
154
|
+
|
155
|
+
def description
|
156
|
+
"expose create action on #{@new_path}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Rao
|
2
|
+
module Shoulda
|
3
|
+
module Matchers
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# RSpec.describe '/posts', type: :feature do
|
7
|
+
# let(:resource_class) { Post }
|
8
|
+
# let(:resource) { create(:post) }
|
9
|
+
#
|
10
|
+
# it {
|
11
|
+
# expect(subject).to implement_delete_action(self)
|
12
|
+
# .for(resource)
|
13
|
+
# .reducing{ resource_class.count }.by(1)
|
14
|
+
# }
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
def implement_delete_action(spec)
|
18
|
+
ImplementDeleteActionMatcher.new(spec)
|
19
|
+
end
|
20
|
+
|
21
|
+
class ImplementDeleteActionMatcher
|
22
|
+
include RSpec::Matchers
|
23
|
+
|
24
|
+
def initialize(spec)
|
25
|
+
@spec = spec
|
26
|
+
end
|
27
|
+
|
28
|
+
# Resource that will be deleted
|
29
|
+
def for(resource)
|
30
|
+
@resource = resource
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def id
|
35
|
+
@resource.to_param
|
36
|
+
end
|
37
|
+
|
38
|
+
def reducing(&block)
|
39
|
+
@block = block
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def by(expected_reduction)
|
44
|
+
@expected_reduction = expected_reduction
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def matches?(base_path)
|
49
|
+
@base_path = @spec.class.name.split('::')[0..2].join('::').constantize.description
|
50
|
+
# @base_path = base_path
|
51
|
+
@expected_path = @base_path
|
52
|
+
@show_path = "#{@base_path}/#{id}"
|
53
|
+
|
54
|
+
@spec.visit(@show_path)
|
55
|
+
|
56
|
+
@before_delete_count = @block.call
|
57
|
+
@spec.find(delete_link_matcher).click
|
58
|
+
@after_delete_count = @block.call
|
59
|
+
|
60
|
+
has_correct_status_code && has_correct_current_path && has_reduced_resource_count
|
61
|
+
end
|
62
|
+
|
63
|
+
def has_correct_status_code
|
64
|
+
begin
|
65
|
+
if @spec.status_code == 200
|
66
|
+
true
|
67
|
+
else
|
68
|
+
@error = "Wrong status code [#{@spec.status_code}] instead of [200]"
|
69
|
+
false
|
70
|
+
end
|
71
|
+
rescue Capybara::NotSupportedByDriverError => e
|
72
|
+
puts "[Warning] Skipping status code check as it is not supported by your driver [#{@spec.driver.instance_variable_get(:@name)}]."
|
73
|
+
return true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def has_reduced_resource_count
|
78
|
+
if (@before_delete_count - @after_delete_count) == @expected_reduction
|
79
|
+
true
|
80
|
+
else
|
81
|
+
@error = "Did not reduce by expected [#{@expected_reduction}] but by [#{@before_delete_count - @after_delete_count}]"
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def has_correct_current_path
|
87
|
+
if @spec.current_path == @expected_path
|
88
|
+
true
|
89
|
+
else
|
90
|
+
@error = "Wrong current path [#{@spec.current_path}] instead of [#{@expected_path}]"
|
91
|
+
false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def failure_message
|
96
|
+
"Should expose delete action on #{@base_path} [#{delete_link_matcher}], clicking on #{delete_link_matcher}. Error: #{@error}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def failure_message_when_negated
|
100
|
+
"Should not expose delete action on #{@base_path} [#{delete_link_matcher}], clicking on #{delete_link_matcher}. Error: #{@error}"
|
101
|
+
end
|
102
|
+
|
103
|
+
alias negative_failure_message failure_message_when_negated
|
104
|
+
|
105
|
+
def description
|
106
|
+
"expose delete action on #{@base_path} [#{delete_link_matcher}]"
|
107
|
+
end
|
108
|
+
|
109
|
+
def delete_link_matcher
|
110
|
+
'a[data-method="delete"]'
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Rao
|
2
|
+
module Shoulda
|
3
|
+
module Matchers
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# RSpec.describe '/posts', type: :feature do
|
7
|
+
# before(:each) { create_list(:post, 3) }
|
8
|
+
#
|
9
|
+
# it { expect(subject).to implement_index_action(self) }
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
def implement_index_action(spec)
|
13
|
+
ImplementIndexActionMatcher.new(spec)
|
14
|
+
end
|
15
|
+
|
16
|
+
class ImplementIndexActionMatcher
|
17
|
+
include RSpec::Matchers
|
18
|
+
|
19
|
+
def initialize(spec)
|
20
|
+
@spec = spec
|
21
|
+
end
|
22
|
+
|
23
|
+
def matches?(base_path)
|
24
|
+
@base_path = @spec.class.name.split('::')[0..2].join('::').constantize.description
|
25
|
+
# @base_path = base_path
|
26
|
+
|
27
|
+
@spec.visit(@base_path)
|
28
|
+
has_correct_status_code && has_correct_current_path
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_correct_status_code
|
32
|
+
begin
|
33
|
+
if @spec.status_code == 200
|
34
|
+
true
|
35
|
+
else
|
36
|
+
@error = "Wrong status code [#{@spec.status_code}] instead of [200]"
|
37
|
+
false
|
38
|
+
end
|
39
|
+
rescue Capybara::NotSupportedByDriverError => e
|
40
|
+
puts "[Warning] Skipping status code check as it is not supported by your driver [#{@spec.driver.instance_variable_get(:@name)}]."
|
41
|
+
return true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def has_correct_current_path
|
46
|
+
if @spec.current_path == @base_path
|
47
|
+
true
|
48
|
+
else
|
49
|
+
@error = "Wrong current path [#{@spec.current_path}] instead of [#{@base_path}]"
|
50
|
+
false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def failure_message
|
55
|
+
"Should expose index action on #{@base_path}. Error: #{@error}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def failure_message_when_negated
|
59
|
+
"Should not expose index action on #{@base_path}. Error: #{@error}"
|
60
|
+
end
|
61
|
+
|
62
|
+
alias negative_failure_message failure_message_when_negated
|
63
|
+
|
64
|
+
def description
|
65
|
+
"expose index action on #{@base_path}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Rao
|
2
|
+
module Shoulda
|
3
|
+
module Matchers
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# RSpec.describe '/posts', type: :feature do
|
7
|
+
# let(:resource) { create(:post) }
|
8
|
+
#
|
9
|
+
# it { expect(subject).to implement_show_action(self).for(resource) }
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
def implement_show_action(spec)
|
13
|
+
ImplementShowActionMatcher.new(spec)
|
14
|
+
end
|
15
|
+
|
16
|
+
class ImplementShowActionMatcher
|
17
|
+
include RSpec::Matchers
|
18
|
+
|
19
|
+
def initialize(spec)
|
20
|
+
@spec = spec
|
21
|
+
end
|
22
|
+
|
23
|
+
# Resource that will be updated
|
24
|
+
def for(resource)
|
25
|
+
@resource = resource
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def id
|
30
|
+
@resource.to_param
|
31
|
+
end
|
32
|
+
|
33
|
+
def matches?(base_path)
|
34
|
+
@base_path = @spec.class.name.split('::')[0..2].join('::').constantize.description
|
35
|
+
# @base_path = base_path
|
36
|
+
@expected_path = "#{@base_path}/#{id}"
|
37
|
+
|
38
|
+
@spec.visit(@expected_path)
|
39
|
+
has_correct_status_code && has_correct_current_path
|
40
|
+
end
|
41
|
+
|
42
|
+
def has_correct_status_code
|
43
|
+
begin
|
44
|
+
if @spec.status_code == 200
|
45
|
+
true
|
46
|
+
else
|
47
|
+
@error = "Wrong status code [#{@spec.status_code}] instead of [200]"
|
48
|
+
false
|
49
|
+
end
|
50
|
+
rescue Capybara::NotSupportedByDriverError => e
|
51
|
+
puts "[Warning] Skipping status code check as it is not supported by your driver [#{@spec.driver.instance_variable_get(:@name)}]."
|
52
|
+
return true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def has_correct_current_path
|
57
|
+
if @spec.current_path == @expected_path
|
58
|
+
true
|
59
|
+
else
|
60
|
+
@error = "Wrong current path [#{@spec.current_path}] instead of [#{@expected_path}]"
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def failure_message
|
66
|
+
"Should expose show action on #{@expected_path}. Error: #{@error}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def failure_message_when_negated
|
70
|
+
"Should not expose show action on #{@expected_path}. Error: #{@error}"
|
71
|
+
end
|
72
|
+
|
73
|
+
alias negative_failure_message failure_message_when_negated
|
74
|
+
|
75
|
+
def description
|
76
|
+
"expose show action on #{@expected_path}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
module Rao
|
2
|
+
module Shoulda
|
3
|
+
module Matchers
|
4
|
+
# Example checking for changed resource attributes:
|
5
|
+
#
|
6
|
+
# RSpec.describe '/posts', type: :feature do
|
7
|
+
# let(:post) { create(:post) }
|
8
|
+
# it {
|
9
|
+
# expect(subject).to implement_update_action(self)
|
10
|
+
# .for(post)
|
11
|
+
# .within_form('.edit_post') {
|
12
|
+
# fill_in 'post[title]', with: 'New title'
|
13
|
+
# fill_in 'post[body]', with: 'New body'
|
14
|
+
# }
|
15
|
+
# .updating
|
16
|
+
# .from(post.attributes)
|
17
|
+
# .to({ 'title' => 'New title', 'body' => 'New body' })
|
18
|
+
# }
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# Example checking for changed state:
|
22
|
+
#
|
23
|
+
# RSpec.describe '/posts', type: :feature do
|
24
|
+
# let(:post) { create(:post) }
|
25
|
+
# it {
|
26
|
+
# expect(subject).to implement_update_action(self)
|
27
|
+
# .for(post)
|
28
|
+
# .within_form('.edit_post') {
|
29
|
+
# fill_in 'post[title]', with: 'New title'
|
30
|
+
# fill_in 'post[body]', with: 'New body'
|
31
|
+
# }
|
32
|
+
# .updating{ |resource| resource.updated_by }
|
33
|
+
# .from(nil)
|
34
|
+
# .to(User.current)
|
35
|
+
# }
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
def implement_update_action(spec)
|
39
|
+
ImplementUpdateActionMatcher.new(spec)
|
40
|
+
end
|
41
|
+
|
42
|
+
class ImplementUpdateActionMatcher
|
43
|
+
include RSpec::Matchers
|
44
|
+
|
45
|
+
def initialize(spec)
|
46
|
+
@spec = spec
|
47
|
+
end
|
48
|
+
|
49
|
+
# Resource that will be updated
|
50
|
+
def for(resource)
|
51
|
+
@resource = resource
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def from(value)
|
56
|
+
@from = value
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def to(value)
|
61
|
+
@to = value
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
# Specifies the form css id to fill to create the resource.
|
66
|
+
def within_form(id, &block)
|
67
|
+
@form_id = id
|
68
|
+
@form_block = block
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def updating(&block)
|
73
|
+
@updating_block = block if block_given?
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
def id
|
78
|
+
@resource.to_param
|
79
|
+
end
|
80
|
+
|
81
|
+
def matches?(base_path)
|
82
|
+
@base_path = @spec.class.name.split('::')[0..2].join('::').constantize.description
|
83
|
+
# @base_path = base_path
|
84
|
+
@show_path = "#{@base_path}/#{id}"
|
85
|
+
@edit_path = "#{@base_path}/#{id}/edit"
|
86
|
+
|
87
|
+
@expected_path = @show_path
|
88
|
+
|
89
|
+
@spec.visit(@edit_path)
|
90
|
+
|
91
|
+
return unless has_correct_attributes_before if @expected_before_attributes.present?
|
92
|
+
|
93
|
+
@spec.within(@form_id) do
|
94
|
+
@form_block.call
|
95
|
+
@spec.find('input[name="commit"]').click
|
96
|
+
end
|
97
|
+
|
98
|
+
@resource.reload
|
99
|
+
|
100
|
+
has_correct_status_code && has_correct_current_path && has_correct_attributes_after
|
101
|
+
end
|
102
|
+
|
103
|
+
def has_correct_status_code
|
104
|
+
begin
|
105
|
+
if @spec.status_code == 200
|
106
|
+
true
|
107
|
+
else
|
108
|
+
@error = "Wrong status code [#{@spec.status_code}] instead of [200]"
|
109
|
+
false
|
110
|
+
end
|
111
|
+
rescue Capybara::NotSupportedByDriverError => e
|
112
|
+
puts "[Warning] Skipping status code check as it is not supported by your driver [#{@spec.driver.instance_variable_get(:@name)}]."
|
113
|
+
return true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def has_correct_current_path
|
118
|
+
if @spec.current_path == @expected_path
|
119
|
+
true
|
120
|
+
else
|
121
|
+
@error = "Wrong current path [#{@spec.current_path}] instead of [#{@expected_path}]"
|
122
|
+
false
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def has_correct_attributes_before
|
127
|
+
expected = @from
|
128
|
+
if @updating_block.present?
|
129
|
+
given = @updating_block.call(@resource)
|
130
|
+
|
131
|
+
if given == expected
|
132
|
+
return true
|
133
|
+
else
|
134
|
+
@error = "Before update state [#{given.inspect}] did not match expected [#{expected.inspect}]"
|
135
|
+
return false
|
136
|
+
end
|
137
|
+
else
|
138
|
+
given = @resource.attributes.with_indifferent_access.slice(*@expected_before_attributes.keys)
|
139
|
+
|
140
|
+
if given == expected
|
141
|
+
true
|
142
|
+
else
|
143
|
+
@error = "Attributes before update [#{given}] did not match expected attributes [#{@expected_before_attributes}]"
|
144
|
+
false
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def has_correct_attributes_after
|
150
|
+
expected = @to
|
151
|
+
if @updating_block.present?
|
152
|
+
given = @updating_block.call(@resource)
|
153
|
+
|
154
|
+
if given == expected
|
155
|
+
return true
|
156
|
+
else
|
157
|
+
@error = "After update state [#{given.inspect}] did not match expected [#{expected.inspect}]"
|
158
|
+
return false
|
159
|
+
end
|
160
|
+
else
|
161
|
+
given = @resource.attributes.with_indifferent_access.slice(*@to.keys)
|
162
|
+
|
163
|
+
if given == expected
|
164
|
+
true
|
165
|
+
else
|
166
|
+
@error = "Attributes after update [#{given}] did not match expected attributes [#{@expected_before_attributes}]"
|
167
|
+
false
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def failure_message
|
173
|
+
"Should expose update action on #{@edit_path}. Error: #{@error}"
|
174
|
+
end
|
175
|
+
|
176
|
+
def failure_message_when_negated
|
177
|
+
"Should not expose update action on #{@edit_path}. Error: #{@error}"
|
178
|
+
end
|
179
|
+
|
180
|
+
alias negative_failure_message failure_message_when_negated
|
181
|
+
|
182
|
+
def description
|
183
|
+
"expose update action on #{@edit_path}"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rao/shoulda/matchers/implement_create_action_matcher'
|
2
|
+
require 'rao/shoulda/matchers/implement_show_action_matcher'
|
3
|
+
require 'rao/shoulda/matchers/implement_update_action_matcher'
|
4
|
+
require 'rao/shoulda/matchers/implement_delete_action_matcher'
|
5
|
+
require 'rao/shoulda/matchers/implement_index_action_matcher'
|
6
|
+
|
7
|
+
module Rao
|
8
|
+
module Shoulda
|
9
|
+
module Matchers
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rao'
|
2
|
+
|
3
|
+
module Rao
|
4
|
+
# Adding the matchers to rspec:
|
5
|
+
#
|
6
|
+
# # spec/rails_helper or spec/support/rao-shoulda_matchers.rb
|
7
|
+
# require 'rao/shoulda/matchers'
|
8
|
+
#
|
9
|
+
# RSpec.configure do |config|
|
10
|
+
# config.include Rao::Shoulda::Matchers, type: :feature
|
11
|
+
# end
|
12
|
+
module ShouldaMatchers
|
13
|
+
end
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rao-shoulda_matchers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.13.pre
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Roberto Vasquez Angel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-03-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rao
|
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: sqlite3
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.3.6
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.3.6
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rails-dummy
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
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-rails
|
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
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: guard-bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
- roberto@vasquez-angel.de
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- MIT-LICENSE
|
119
|
+
- Rakefile
|
120
|
+
- lib/generators/rao/shoulda_matchers/resources_controller_generator.rb
|
121
|
+
- lib/generators/rao/shoulda_matchers/templates/resources_controller_spec.rb
|
122
|
+
- lib/rao-shoulda_matchers.rb
|
123
|
+
- lib/rao/shoulda/matchers.rb
|
124
|
+
- lib/rao/shoulda/matchers/implement_create_action_matcher.rb
|
125
|
+
- lib/rao/shoulda/matchers/implement_delete_action_matcher.rb
|
126
|
+
- lib/rao/shoulda/matchers/implement_index_action_matcher.rb
|
127
|
+
- lib/rao/shoulda/matchers/implement_show_action_matcher.rb
|
128
|
+
- lib/rao/shoulda/matchers/implement_update_action_matcher.rb
|
129
|
+
- lib/rao/shoulda_matchers.rb
|
130
|
+
- lib/rao/shoulda_matchers/version.rb
|
131
|
+
homepage: https://github.com/rao
|
132
|
+
licenses:
|
133
|
+
- MIT
|
134
|
+
metadata: {}
|
135
|
+
post_install_message:
|
136
|
+
rdoc_options: []
|
137
|
+
require_paths:
|
138
|
+
- lib
|
139
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">"
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: 1.3.1
|
149
|
+
requirements: []
|
150
|
+
rubygems_version: 3.0.2
|
151
|
+
signing_key:
|
152
|
+
specification_version: 4
|
153
|
+
summary: Additional shoulda matchers for Ruby on Rails.
|
154
|
+
test_files: []
|