shoulda-matchers 2.3.0 → 2.4.0.rc1
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/Appraisals +12 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +6 -11
- data/NEWS.md +6 -0
- data/features/rails_integration.feature +2 -3
- data/features/step_definitions/rails_steps.rb +40 -8
- data/gemfiles/3.0.gemfile +2 -1
- data/gemfiles/3.0.gemfile.lock +6 -6
- data/gemfiles/3.1.gemfile +2 -1
- data/gemfiles/3.1.gemfile.lock +6 -6
- data/gemfiles/3.2.gemfile +2 -1
- data/gemfiles/3.2.gemfile.lock +6 -6
- data/gemfiles/4.0.gemfile +17 -0
- data/gemfiles/4.0.gemfile.lock +152 -0
- data/lib/shoulda/matchers.rb +1 -0
- data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +1 -1
- data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +24 -10
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +13 -7
- data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +2 -2
- data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +18 -6
- data/lib/shoulda/matchers/active_model/validation_matcher.rb +4 -2
- data/lib/shoulda/matchers/active_record.rb +1 -0
- data/lib/shoulda/matchers/active_record/association_matcher.rb +18 -37
- data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +8 -3
- data/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +8 -3
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +66 -4
- data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +93 -0
- data/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +8 -3
- data/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +7 -2
- data/lib/shoulda/matchers/integrations/test_unit.rb +10 -22
- data/lib/shoulda/matchers/rails_shim.rb +41 -0
- data/lib/shoulda/matchers/version.rb +1 -1
- data/shoulda-matchers.gemspec +1 -2
- data/spec/shoulda/matchers/action_controller/render_with_layout_matcher_spec.rb +7 -2
- data/spec/shoulda/matchers/action_controller/route_matcher_spec.rb +2 -2
- data/spec/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +1 -1
- data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +24 -0
- data/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +24 -0
- data/spec/shoulda/matchers/active_model/helpers_spec.rb +5 -7
- data/spec/shoulda/matchers/active_record/association_matcher_spec.rb +46 -12
- data/spec/shoulda/matchers/active_record/have_db_column_matcher_spec.rb +1 -1
- data/spec/support/active_model_versions.rb +6 -6
- metadata +26 -55
@@ -0,0 +1,93 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module Matchers
|
3
|
+
module ActiveRecord # :nodoc:
|
4
|
+
module AssociationMatchers
|
5
|
+
class OptionVerifier
|
6
|
+
delegate :reflection, to: :reflector
|
7
|
+
|
8
|
+
attr_reader :reflector
|
9
|
+
|
10
|
+
RELATION_OPTIONS = [:conditions, :order]
|
11
|
+
|
12
|
+
def initialize(reflector)
|
13
|
+
@reflector = reflector
|
14
|
+
end
|
15
|
+
|
16
|
+
def correct_for_string?(name, expected_value)
|
17
|
+
correct_for?(:string, name, expected_value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def correct_for_boolean?(name, expected_value)
|
21
|
+
correct_for?(:boolean, name, expected_value)
|
22
|
+
end
|
23
|
+
|
24
|
+
def correct_for_hash?(name, expected_value)
|
25
|
+
correct_for?(:hash, name, expected_value)
|
26
|
+
end
|
27
|
+
|
28
|
+
def correct_for_relation_clause?(name, expected_value)
|
29
|
+
correct_for?(:relation_clause, name, expected_value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def actual_value_for(name)
|
33
|
+
if RELATION_OPTIONS.include?(name)
|
34
|
+
actual_value_for_relation_clause(name)
|
35
|
+
else
|
36
|
+
method_name = "actual_value_for_#{name}"
|
37
|
+
if respond_to?(method_name, true)
|
38
|
+
__send__(method_name)
|
39
|
+
else
|
40
|
+
reflection.options[name]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_reader :reflector
|
48
|
+
|
49
|
+
def correct_for?(*args)
|
50
|
+
expected_value, name, type = args.reverse
|
51
|
+
if expected_value.nil?
|
52
|
+
true
|
53
|
+
else
|
54
|
+
expected_value = type_cast(type, expected_value_for(name, expected_value))
|
55
|
+
actual_value = type_cast(type, actual_value_for(name))
|
56
|
+
expected_value == actual_value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def type_cast(type, value)
|
61
|
+
case type
|
62
|
+
when :string, :relation_clause then value.to_s
|
63
|
+
when :boolean then !!value
|
64
|
+
when :hash then Hash(value).stringify_keys
|
65
|
+
else value
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def expected_value_for(name, value)
|
70
|
+
if RELATION_OPTIONS.include?(name)
|
71
|
+
expected_value_for_relation_clause(name, value)
|
72
|
+
else
|
73
|
+
value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def expected_value_for_relation_clause(name, value)
|
78
|
+
relation = reflector.build_relation_with_clause(name, value)
|
79
|
+
reflector.extract_relation_clause_from(relation, name)
|
80
|
+
end
|
81
|
+
|
82
|
+
def actual_value_for_relation_clause(name)
|
83
|
+
reflector.extract_relation_clause_from(reflector.association_relation, name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def actual_value_for_class_name
|
87
|
+
reflector.associated_class
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -16,9 +16,9 @@ module Shoulda # :nodoc:
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def matches?(subject)
|
19
|
-
subject = ModelReflector.new(subject, name)
|
19
|
+
self.subject = ModelReflector.new(subject, name)
|
20
20
|
|
21
|
-
if
|
21
|
+
if option_verifier.correct_for_relation_clause?(:order, order)
|
22
22
|
true
|
23
23
|
else
|
24
24
|
self.missing_option = "#{name} should be ordered by #{order}"
|
@@ -27,7 +27,12 @@ module Shoulda # :nodoc:
|
|
27
27
|
end
|
28
28
|
|
29
29
|
private
|
30
|
-
|
30
|
+
|
31
|
+
attr_accessor :subject, :order, :name
|
32
|
+
|
33
|
+
def option_verifier
|
34
|
+
@option_verifier ||= OptionVerifier.new(subject)
|
35
|
+
end
|
31
36
|
end
|
32
37
|
end
|
33
38
|
end
|
@@ -38,18 +38,23 @@ module Shoulda # :nodoc:
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def through_association_correct?
|
41
|
-
if
|
41
|
+
if option_verifier.correct_for_string?(:through, through)
|
42
42
|
true
|
43
43
|
else
|
44
44
|
self.missing_option =
|
45
45
|
"Expected #{name} to have #{name} through #{through}, " +
|
46
|
-
"but got it through #{
|
46
|
+
"but got it through #{option_verifier.actual_value_for(:through)}"
|
47
47
|
false
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
51
|
private
|
52
|
+
|
52
53
|
attr_accessor :through, :name, :subject
|
54
|
+
|
55
|
+
def option_verifier
|
56
|
+
@option_verifier ||= OptionVerifier.new(subject)
|
57
|
+
end
|
53
58
|
end
|
54
59
|
end
|
55
60
|
end
|
@@ -1,10 +1,7 @@
|
|
1
|
-
# :enddoc:
|
2
|
-
require 'test/unit/testcase'
|
3
|
-
|
4
1
|
if defined?(ActionController)
|
5
2
|
require 'shoulda/matchers/action_controller'
|
6
3
|
|
7
|
-
|
4
|
+
ActionController::TestCase.class_eval do
|
8
5
|
include Shoulda::Matchers::ActionController
|
9
6
|
extend Shoulda::Matchers::ActionController
|
10
7
|
|
@@ -16,27 +13,18 @@ end
|
|
16
13
|
|
17
14
|
if defined?(ActiveRecord)
|
18
15
|
require 'shoulda/matchers/active_record'
|
19
|
-
require 'shoulda/matchers/active_model'
|
20
16
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
include Shoulda::Matchers::ActiveRecord
|
25
|
-
extend Shoulda::Matchers::ActiveRecord
|
26
|
-
include Shoulda::Matchers::ActiveModel
|
27
|
-
extend Shoulda::Matchers::ActiveModel
|
28
|
-
end
|
29
|
-
end
|
17
|
+
ActiveSupport::TestCase.class_eval do
|
18
|
+
include Shoulda::Matchers::ActiveRecord
|
19
|
+
extend Shoulda::Matchers::ActiveRecord
|
30
20
|
end
|
31
|
-
|
21
|
+
end
|
22
|
+
|
23
|
+
if defined?(ActiveModel)
|
32
24
|
require 'shoulda/matchers/active_model'
|
33
25
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
include Shoulda::Matchers::ActiveModel
|
38
|
-
extend Shoulda::Matchers::ActiveModel
|
39
|
-
end
|
40
|
-
end
|
26
|
+
ActiveSupport::TestCase.class_eval do
|
27
|
+
include Shoulda::Matchers::ActiveModel
|
28
|
+
extend Shoulda::Matchers::ActiveModel
|
41
29
|
end
|
42
30
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module Matchers
|
3
|
+
class RailsShim # :nodoc:
|
4
|
+
def self.layouts_ivar
|
5
|
+
if rails_major_version >= 4
|
6
|
+
'@_layouts'
|
7
|
+
else
|
8
|
+
'@layouts'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.flashes_ivar
|
13
|
+
if rails_major_version >= 4
|
14
|
+
:@flashes
|
15
|
+
else
|
16
|
+
:@used
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.clean_scope(klass)
|
21
|
+
if rails_major_version == 4
|
22
|
+
klass.all
|
23
|
+
else
|
24
|
+
klass.scoped
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.validates_confirmation_of_error_attribute(matcher)
|
29
|
+
if rails_major_version == 4
|
30
|
+
matcher.confirmation_attribute
|
31
|
+
else
|
32
|
+
matcher.attribute
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.rails_major_version
|
37
|
+
Rails::VERSION::MAJOR
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/shoulda-matchers.gemspec
CHANGED
@@ -28,6 +28,5 @@ Gem::Specification.new do |s|
|
|
28
28
|
s.add_development_dependency('cucumber', '~> 1.1')
|
29
29
|
s.add_development_dependency('rails', '~> 3.0')
|
30
30
|
s.add_development_dependency('rake', '>= 0.9.2')
|
31
|
-
s.add_development_dependency('rspec-rails', '
|
32
|
-
s.add_development_dependency('strong_parameters')
|
31
|
+
s.add_development_dependency('rspec-rails', '>= 2.13.1', '< 3')
|
33
32
|
end
|
@@ -47,8 +47,13 @@ describe Shoulda::Matchers::ActionController::RenderWithLayoutMatcher do
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def set_in_context_layout(layout)
|
50
|
-
|
51
|
-
|
50
|
+
layouts = Hash.new(0)
|
51
|
+
layouts[layout] = 1
|
52
|
+
self.instance_variable_set(layouts_ivar, layouts)
|
53
|
+
end
|
54
|
+
|
55
|
+
def layouts_ivar
|
56
|
+
Shoulda::Matchers::RailsShim.layouts_ivar
|
52
57
|
end
|
53
58
|
|
54
59
|
def controller_without_layout
|
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Shoulda::Matchers::ActionController::RouteMatcher do
|
3
|
+
describe Shoulda::Matchers::ActionController::RouteMatcher, type: :controller do
|
4
4
|
context 'given a controller with a defined glob url' do
|
5
5
|
it 'accepts glob route' do
|
6
6
|
controller = define_controller('Examples').new
|
7
7
|
|
8
8
|
define_routes do
|
9
|
-
|
9
|
+
get 'examples/*id', :to => 'examples#example'
|
10
10
|
end
|
11
11
|
|
12
12
|
controller.should route(:get, '/examples/foo/bar').
|
@@ -59,7 +59,7 @@ describe Shoulda::Matchers::ActiveModel::AllowMassAssignmentOfMatcher do
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
-
unless active_model_3_2?
|
62
|
+
unless active_model_3_2? || active_model_4_0?
|
63
63
|
context 'an attribute on a class with no protected attributes' do
|
64
64
|
it 'accepts being mass-assignable' do
|
65
65
|
no_protected_attributes.should allow_mass_assignment_of(:attr)
|
@@ -56,6 +56,30 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher do
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
+
context 'an attribute where the message occurs on another attribute' do
|
60
|
+
it 'allows a good value' do
|
61
|
+
record_with_custom_validation.should \
|
62
|
+
allow_value('good value').for(:attr).with_message(/some message/, :against => :attr2)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'rejects a bad value' do
|
66
|
+
record_with_custom_validation.should_not \
|
67
|
+
allow_value('bad value').for(:attr).with_message(/some message/, :against => :attr2)
|
68
|
+
end
|
69
|
+
|
70
|
+
def record_with_custom_validation
|
71
|
+
define_model :example, :attr => :string, :attr2 => :string do
|
72
|
+
validate :custom_validation
|
73
|
+
|
74
|
+
def custom_validation
|
75
|
+
if self[:attr] != 'good value'
|
76
|
+
self.errors[:attr2] << 'some message'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end.new
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
59
83
|
context "an attribute with a context-dependent validation" do
|
60
84
|
context "without the validation context" do
|
61
85
|
it "allows a bad value" do
|
@@ -55,6 +55,30 @@ describe Shoulda::Matchers::ActiveModel::DisallowValueMatcher do
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
+
context 'an attribute where the message occurs on another attribute' do
|
59
|
+
it 'matches if the message is correct but the value is not' do
|
60
|
+
record_with_custom_validation.should \
|
61
|
+
matcher('bad value').for(:attr).with_message(/some message/, :against => :attr2)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'does not match if the value and message are both correct' do
|
65
|
+
record_with_custom_validation.should_not \
|
66
|
+
matcher('good value').for(:attr).with_message(/some message/, :against => :attr2)
|
67
|
+
end
|
68
|
+
|
69
|
+
def record_with_custom_validation
|
70
|
+
define_model :example, :attr => :string, :attr2 => :string do
|
71
|
+
validate :custom_validation
|
72
|
+
|
73
|
+
def custom_validation
|
74
|
+
if self[:attr] != 'good value'
|
75
|
+
self.errors[:attr2] << 'some message'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end.new
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
58
82
|
def matcher(value)
|
59
83
|
described_class.new(value)
|
60
84
|
end
|
@@ -78,13 +78,11 @@ describe Shoulda::Matchers::ActiveModel::Helpers do
|
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
|
-
if
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
assert_presence_validation_has_correct_message
|
87
|
-
end
|
81
|
+
context 'if ActiveModel::Errors#generate_message behavior has changed' do
|
82
|
+
it 'provides the right error message for validate_presence_of' do
|
83
|
+
stub_active_model_message_generation(:type => :blank,
|
84
|
+
:message => 'Behavior has diverged.')
|
85
|
+
assert_presence_validation_has_correct_message
|
88
86
|
end
|
89
87
|
end
|
90
88
|
end
|
@@ -67,8 +67,8 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
|
67
67
|
|
68
68
|
it 'accepts an association with a valid :conditions option' do
|
69
69
|
define_model :parent, :adopter => :boolean
|
70
|
-
define_model
|
71
|
-
belongs_to :parent, :
|
70
|
+
define_model(:child, :parent_id => :integer).tap do |model|
|
71
|
+
define_association_with_conditions(model, :belongs_to, :parent, :adopter => true)
|
72
72
|
end
|
73
73
|
|
74
74
|
Child.new.should belong_to(:parent).conditions(:adopter => true)
|
@@ -297,8 +297,8 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
|
297
297
|
|
298
298
|
it 'accepts an association with a valid :conditions option' do
|
299
299
|
define_model :child, :parent_id => :integer, :adopted => :boolean
|
300
|
-
define_model
|
301
|
-
has_many :children, :
|
300
|
+
define_model(:parent).tap do |model|
|
301
|
+
define_association_with_conditions(model, :has_many, :children, :adopted => true)
|
302
302
|
end
|
303
303
|
|
304
304
|
Parent.new.should have_many(:children).conditions(:adopted => true)
|
@@ -374,8 +374,13 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
|
374
374
|
|
375
375
|
def having_many_children(options = {})
|
376
376
|
define_model :child, :parent_id => :integer
|
377
|
-
define_model
|
378
|
-
|
377
|
+
define_model(:parent).tap do |model|
|
378
|
+
if options.key?(:order)
|
379
|
+
order = options.delete(:order)
|
380
|
+
define_association_with_order(model, :has_many, :children, order, options)
|
381
|
+
else
|
382
|
+
model.has_many :children, options
|
383
|
+
end
|
379
384
|
end.new
|
380
385
|
end
|
381
386
|
end
|
@@ -449,8 +454,8 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
|
449
454
|
|
450
455
|
it 'accepts an association with a valid :conditions option' do
|
451
456
|
define_model :detail, :person_id => :integer, :disabled => :boolean
|
452
|
-
define_model
|
453
|
-
has_one :detail, :
|
457
|
+
define_model(:person).tap do |model|
|
458
|
+
define_association_with_conditions(model, :has_one, :detail, :disabled => true)
|
454
459
|
end
|
455
460
|
|
456
461
|
Person.new.should have_one(:detail).conditions(:disabled => true)
|
@@ -524,8 +529,13 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
|
524
529
|
|
525
530
|
def having_one_detail(options = {})
|
526
531
|
define_model :detail, :person_id => :integer
|
527
|
-
define_model
|
528
|
-
|
532
|
+
define_model(:person).tap do |model|
|
533
|
+
if options.key?(:order)
|
534
|
+
order = options.delete(:order)
|
535
|
+
define_association_with_order(model, :has_one, :detail, order, options)
|
536
|
+
else
|
537
|
+
model.has_one :detail, options
|
538
|
+
end
|
529
539
|
end.new
|
530
540
|
end
|
531
541
|
end
|
@@ -565,8 +575,8 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
|
565
575
|
|
566
576
|
it 'accepts an association with a valid :conditions option' do
|
567
577
|
define_model :relative, :adopted => :boolean
|
568
|
-
define_model
|
569
|
-
has_and_belongs_to_many :relatives, :
|
578
|
+
define_model(:person).tap do |model|
|
579
|
+
define_association_with_conditions(model, :has_and_belongs_to_many, :relatives, :adopted => true)
|
570
580
|
end
|
571
581
|
define_model :people_relative, :id => false, :person_id => :integer,
|
572
582
|
:relative_id => :integer
|
@@ -638,4 +648,28 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
|
638
648
|
end.new
|
639
649
|
end
|
640
650
|
end
|
651
|
+
|
652
|
+
def define_association_with_conditions(model, macro, name, conditions, other_options={})
|
653
|
+
args = []
|
654
|
+
options = {}
|
655
|
+
if Shoulda::Matchers::RailsShim.rails_major_version == 4
|
656
|
+
args << lambda { where(conditions) }
|
657
|
+
else
|
658
|
+
options[:conditions] = conditions
|
659
|
+
end
|
660
|
+
args << options
|
661
|
+
model.__send__(macro, name, *args)
|
662
|
+
end
|
663
|
+
|
664
|
+
def define_association_with_order(model, macro, name, order, other_options={})
|
665
|
+
args = []
|
666
|
+
options = {}
|
667
|
+
if Shoulda::Matchers::RailsShim.rails_major_version == 4
|
668
|
+
args << lambda { order(order) }
|
669
|
+
else
|
670
|
+
options[:order] = order
|
671
|
+
end
|
672
|
+
args << options
|
673
|
+
model.__send__(macro, name, *args)
|
674
|
+
end
|
641
675
|
end
|