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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/Appraisals +12 -0
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +6 -11
  5. data/NEWS.md +6 -0
  6. data/features/rails_integration.feature +2 -3
  7. data/features/step_definitions/rails_steps.rb +40 -8
  8. data/gemfiles/3.0.gemfile +2 -1
  9. data/gemfiles/3.0.gemfile.lock +6 -6
  10. data/gemfiles/3.1.gemfile +2 -1
  11. data/gemfiles/3.1.gemfile.lock +6 -6
  12. data/gemfiles/3.2.gemfile +2 -1
  13. data/gemfiles/3.2.gemfile.lock +6 -6
  14. data/gemfiles/4.0.gemfile +17 -0
  15. data/gemfiles/4.0.gemfile.lock +152 -0
  16. data/lib/shoulda/matchers.rb +1 -0
  17. data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +1 -1
  18. data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +24 -10
  19. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +13 -7
  20. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +2 -2
  21. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +18 -6
  22. data/lib/shoulda/matchers/active_model/validation_matcher.rb +4 -2
  23. data/lib/shoulda/matchers/active_record.rb +1 -0
  24. data/lib/shoulda/matchers/active_record/association_matcher.rb +18 -37
  25. data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +8 -3
  26. data/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +8 -3
  27. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +66 -4
  28. data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +93 -0
  29. data/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +8 -3
  30. data/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +7 -2
  31. data/lib/shoulda/matchers/integrations/test_unit.rb +10 -22
  32. data/lib/shoulda/matchers/rails_shim.rb +41 -0
  33. data/lib/shoulda/matchers/version.rb +1 -1
  34. data/shoulda-matchers.gemspec +1 -2
  35. data/spec/shoulda/matchers/action_controller/render_with_layout_matcher_spec.rb +7 -2
  36. data/spec/shoulda/matchers/action_controller/route_matcher_spec.rb +2 -2
  37. data/spec/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +1 -1
  38. data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +24 -0
  39. data/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +24 -0
  40. data/spec/shoulda/matchers/active_model/helpers_spec.rb +5 -7
  41. data/spec/shoulda/matchers/active_record/association_matcher_spec.rb +46 -12
  42. data/spec/shoulda/matchers/active_record/have_db_column_matcher_spec.rb +1 -1
  43. data/spec/support/active_model_versions.rb +6 -6
  44. 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 subject.option_set_properly?(order, :order)
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
- attr_accessor :order, :name
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 subject.option_set_properly?(through, :through)
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 #{subject.option_string(: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
- class ActionController::TestCase
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
- module Test
22
- module Unit
23
- class TestCase
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
- elsif defined?(ActiveModel)
21
+ end
22
+
23
+ if defined?(ActiveModel)
32
24
  require 'shoulda/matchers/active_model'
33
25
 
34
- module Test
35
- module Unit
36
- class TestCase
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
@@ -1,5 +1,5 @@
1
1
  module Shoulda
2
2
  module Matchers
3
- VERSION = '2.3.0'.freeze
3
+ VERSION = '2.4.0.rc1'.freeze
4
4
  end
5
5
  end
@@ -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', '~> 2.13')
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
- @layouts = Hash.new(0)
51
- @layouts[layout] = 1
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
- match 'examples/*id', :to => 'examples#example'
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 active_model_3_0?
82
- context 'if ActiveModel::Errors#generate_message behavior has changed' do
83
- it 'provides the right error message for validate_presence_of' do
84
- stub_active_model_message_generation(:type => :blank,
85
- :message => 'Behavior has diverged.')
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 :child, :parent_id => :integer do
71
- belongs_to :parent, :conditions => { :adopter => true }
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 :parent do
301
- has_many :children, :conditions => { :adopted => true }
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 :parent do
378
- has_many :children, options
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 :person do
453
- has_one :detail, :conditions => { :disabled => true}
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 :person do
528
- has_one :detail, options
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 :person do
569
- has_and_belongs_to_many :relatives, :conditions => { :adopted => true }
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